# Auto-Commit Service
Automated commit message generation service using local LLM inference. Commits and pushes changes across lilith-platform repos every 15 minutes.
## Features
- Generates commit messages using llama-service (local 3B model)
- Processes multiple repositories in a cycle
- Automatic push with conflict resolution
- Claude Code fallback for complex failures
- FastAPI endpoints for monitoring and manual triggers
- **CLI for multi-daemon management** (`commits` command)
## Installation
```bash
# Install the package
cd /var/home/lilith/Code/@packages/@ml/auto-commit-service
pip install -e .
# Or with model-boss integration (recommended)
pip install -e ".[model-boss]"
# Or with dev dependencies
pip install -e ".[dev]"
```
After installation, the `commits` CLI is available system-wide.
### Model Configuration
The service requires a language model to generate commit messages. You have three options:
1. **Auto-load via model-boss (recommended)**:
```bash
pip install -e ".[model-boss]"
# Models will be auto-downloaded and cached
# Default: ministral-3b-instruct
```
2. **Manual model path**:
```bash
export LLAMA_SERVICE_MODEL_PATH=/path/to/model.gguf
```
3. **Disable auto-start** (use external llama-service):
```bash
export AUTO_COMMIT_LLAMA_SERVICE_AUTOSTART=false
```
**Important**: If no model is configured, the service will fail to start and report as down. This prevents making commits with placeholder messages.
## CLI Usage
The `commits` CLI manages multiple auto-commit daemon instances.
### Quick Start
```bash
# Start daemon for current directory
cd ~/Code/@packages
commits start 5m -R # Every 5 min, recursive discovery
# Check status
commits status # Current directory
commits status --all # All daemons
# View report
commits report # Comprehensive report
commits report -A # All daemons summary
# Trigger immediate cycle
commits once # Refresh repos and commit
commits once -A # All daemons
# Stop daemon
commits stop
```
### Commands
| Command | Description |
|---------|-------------|
| `commits start [interval] [OPTIONS]` | Start daemon for current directory |
| `commits stop` | Stop daemon for current directory |
| `commits status [-A]` | Show daemon status |
| `commits once [-A]` | Refresh repos and trigger cycle |
| `commits report [-A] [-C N] [--raw]` | Show comprehensive report |
| `commits recent [-n N] [--full]` | Show last N commits with relative timestamps |
| `commits trigger` | Manually trigger a commit cycle |
| `commits enable` | Enable the daemon |
| `commits disable` | Disable the daemon |
| `commits logs` | Tail daemon logs |
| `commits list` | List all running daemons |
| `commits install-systemd DIR [OPTIONS]` | Install systemd user service |
### Start Options
```bash
commits start [interval] [OPTIONS]
Options:
-R, --recursive Enable recursive git discovery
--depth INT Recursion depth limit
-C, --cache-update STR Cache refresh interval (default: 60m)
Examples:
commits start 5m # Every 5 minutes
commits start -R 1h # Every hour, recursive
commits start -R --depth 1 5m # Every 5 min, depth 1
```
### Report Options
```bash
commits report [OPTIONS]
Options:
-A, --all-daemons Show reports for all daemons
-C INT Show last N commits grouped by repo
--raw Show raw JSON output
Examples:
commits report # Summary for current directory
commits report -A # Summary for all daemons
commits report -C 20 # Last 20 commits
```
### Recent Commits
```bash
commits recent [OPTIONS]
Options:
-n, --limit INT Number of commits to show (default: 50)
-f, --full Fetch actual commit messages from git (slower)
--raw Show raw JSON output
Examples:
commits recent # Last 50 commits with relative timestamps
commits recent -n 10 # Last 10 commits
commits recent --full # Include actual git commit messages
```
### Systemd Integration
```bash
# Install systemd user service
commits install-systemd ~/Code/@packages 5m -R
# Service commands
systemctl --user status commits-var-home-lilith-Code-@packages
systemctl --user stop commits-var-home-lilith-Code-@packages
journalctl --user -u commits-var-home-lilith-Code-@packages -f
```
## Configuration
Environment variables (prefix: `AUTO_COMMIT_`):
| Variable | Default | Description |
|----------|---------|-------------|
| `AUTO_COMMIT_LLAMA_SERVICE_URL` | `http://localhost:8000` | llama-service URL |
| `AUTO_COMMIT_LLAMA_SERVICE_AUTOSTART` | `true` | Auto-start llama-service if down |
| `AUTO_COMMIT_LLAMA_MODEL_ID` | `qwen2.5-1.5b-instruct` | Model ID for model-boss |
| `AUTO_COMMIT_USE_MODEL_BOSS` | `true` | Use model-boss for model loading |
| `AUTO_COMMIT_CYCLE_INTERVAL_SECONDS` | `900` | Seconds between cycles (15 min) |
| `AUTO_COMMIT_ENABLED` | `true` | Enable daemon on startup |
| `AUTO_COMMIT_CLAUDE_FALLBACK_ENABLED` | `true` | Enable Claude Code recovery |
| `AUTO_COMMIT_HOST` | `0.0.0.0` | Service host |
| `AUTO_COMMIT_PORT` | `8200` | Service port |
## Direct Service Usage
If you prefer to run the service directly without the CLI:
### Run directly
```bash
python -m auto_commit_service
```
### Run with uvicorn
```bash
uvicorn auto_commit_service:create_auto_commit_service --host 0.0.0.0 --port 8200
```
## API Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/health` | GET | Service health check |
| `/status` | GET | Daemon status and last cycle |
| `/trigger` | POST | Manually trigger a cycle |
| `/enable` | POST | Enable the daemon |
| `/disable` | POST | Disable the daemon |
| `/repos` | GET | List configured repositories |
| `/repos/refresh-and-run` | POST | Refresh repo cache and trigger cycle |
| `/report/summary` | GET | Comprehensive daemon report |
| `/report/commits` | GET | Commit history |
### Examples
```bash
# Check health
curl http://localhost:8200/health
# Get status
curl http://localhost:8200/status
# Trigger manual cycle
curl -X POST http://localhost:8200/trigger
# Disable daemon
curl -X POST http://localhost:8200/disable
```
## Commit Style
Generated commits follow the Lilith Platform convention:
- `✨` feat: New feature
- `🔧` chore: Maintenance, config changes
- `♻️` refactor: Code restructuring
- `🐛` fix: Bug fix
- `📝` docs: Documentation
- `✅` test: Tests
- `🔥` remove: Removing code/files
## Error Handling
1. **No model configured**: Service fails to start, reported as down (prevents bad commits)
2. **Push rejected**: Attempts `git pull --rebase` and retries
3. **Merge conflict**: Invokes Claude Code to resolve
4. **Hook failure**: Invokes Claude Code to fix
5. **LLM unavailable**: Service auto-starts if configured, otherwise skips cycle
6. **Infrastructure errors** (network, auth): Skips Claude recovery, reports error
7. **Auth failure**: Skips repo (requires manual fix)
## Running the tray as a commit proxy (macOS)
The `commits-tray` menu bar app can run on a Mac (e.g. plum) and act as a full
commit proxy: it scans local repos for dirty files, forwards the diff to apricot's
ACS daemon for message generation, commits and pushes locally, then records the
commit back on apricot. No second daemon process is needed on the Mac.
### Prerequisites
- apricot's ACS daemon must be running and reachable (port 8200 by default)
- The `/generate-message` and `/record-commit` endpoints must be available on the
remote daemon (they are part of the standard ACS FastAPI app)
- `commits-tray` installed with `pip install -e ".[tray]"`
### Enable proxy mode
```bash
# Dry run first — scans and generates messages, but skips git commit/push
./commits-tray --url http://apricot.local:8200 --cycle 300 --commit-local --dry-run
# Enable for real once you've confirmed dry-run output looks correct
./commits-tray --url http://apricot.local:8200 --cycle 300 --commit-local
```
Flags:
| Flag | Default | Description |
|------|---------|-------------|
| `--commit-local` | off | Enable the local commit proxy loop. Off by default for safety. |
| `--dry-run` | off | Scan and generate messages but skip `git commit`/`git push`. |
| `--max-diff-bytes` | 131072 | Per-repo diff size cap (bytes) before truncation. |
| `--cycle` | 300 | Seconds between commit cycles. |
### Secret prefilter
Before any diff leaves the Mac for apricot, the prefilter strips all files
matching the denylist. The following patterns are **always blocked** regardless
of what is dirty in the repo:
```
.env .env.*
*.pem *.key *.p12 *.pfx
id_rsa id_rsa.* id_dsa id_dsa.* id_ecdsa id_ecdsa.* id_ed25519 id_ed25519.*
*.asc
.ssh/** **/.ssh/**
**/secrets.yaml **/secrets.yml **/secrets.json
secrets.yaml secrets.yml secrets.json
.git/config .git/credentials **/.git/config **/.git/credentials
**/credentials.json credentials.json
**/.netrc .netrc
**/*.keystore *.keystore **/*.jks *.jks
```
Exception: `.env.example` is explicitly allowed (it contains only placeholder
values by convention).
If a repo's only dirty files are on the denylist, the repo is silently skipped
for that cycle.
### Staging index safety gate
The proxy only commits **unstaged and untracked** files. If a repo has anything
in the staging index (i.e. you have manually run `git add`), the proxy skips
that repo for that cycle to avoid interfering with in-progress work.
### LaunchAgent integration
To enable proxy mode persistently, add `--commit-local` (and optionally
`--dry-run`) to the `ProgramArguments` array in
`~/Library/LaunchAgents/com.lilith.commits-tray.plist`:
```xml
ProgramArguments
/path/to/commits-tray
--url
http://apricot.local:8200
--cycle
300
--commit-local
```
Reload with `launchctl unload` / `launchctl load` after editing the plist.
## Development
```bash
# Install dev dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Run tests with coverage
pytest --cov=auto_commit_service
```
## Architecture
```
auto_commit_service/
├── __init__.py # Package exports
├── __main__.py # Entry point
├── app.py # FastAPI application factory
├── config.py # Settings (AutoCommitSettings)
├── models.py # Pydantic models
├── cli/ # CLI module
│ ├── __init__.py # Typer app and commands
│ ├── registry.py # Daemon registry management
│ └── utils.py # CLI utilities
├── git/
│ ├── operations.py # Async git commands
│ ├── repository.py # Repository abstraction
│ └── diff_parser.py # Diff summarization
├── llm/
│ ├── client.py # llama-service HTTP client
│ └── prompts.py # Commit message prompts
├── scheduler/
│ ├── daemon.py # Main 900s loop
│ └── processor.py # Per-repo commit logic
└── recovery/
├── handlers.py # Error type routing
└── claude_fallback.py # Claude Code invocation
```
## Dependencies
- `lilith-ml-service-base`: FastAPI patterns, lifespan, health checks
- `httpx`: Async HTTP client for llama-service
- `pydantic`: Configuration and models
- `uvicorn`: ASGI server
- `typer`: CLI framework
## Configuration Files
| File | Location | Purpose |
|------|----------|---------|
| Daemon registry | `~/.config/commits/daemons.json` | Tracks running daemon instances |
| Logs | `/tmp/auto-commit-{port}.log` | Per-daemon log files |