Compare commits
2 commits
6758f10a7e
...
b64859a2a5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b64859a2a5 | ||
| 92812ccc34 |
4 changed files with 113 additions and 27 deletions
|
|
@ -9,6 +9,8 @@ You are helping the user set up and operate a **disinto autonomous code factory*
|
|||
of bash scripts and Claude CLI that automates the full development lifecycle: picking up
|
||||
issues, implementing via Claude, creating PRs, running CI, reviewing, merging, and mirroring.
|
||||
|
||||
This guide shows how to set up the factory to develop an **external project** (e.g., `johba/harb`).
|
||||
|
||||
## First-time setup
|
||||
|
||||
Walk the user through these steps interactively. Ask questions where marked with [ASK].
|
||||
|
|
@ -27,20 +29,34 @@ docker --version && git --version && jq --version && curl --version && tmux -V &
|
|||
|
||||
Any missing tool — help the user install it before continuing.
|
||||
|
||||
### 2. Clone and init
|
||||
### 2. Clone disinto and choose a target project
|
||||
|
||||
Clone the disinto factory itself:
|
||||
```bash
|
||||
git clone https://codeberg.org/johba/disinto.git && cd disinto
|
||||
```
|
||||
|
||||
[ASK] What repo should the factory develop? Options:
|
||||
- **Itself** (self-development): `bin/disinto init https://codeberg.org/johba/disinto --yes --repo-root $(pwd)`
|
||||
- **Another project**: `bin/disinto init <repo-url> --yes`
|
||||
[ASK] What repository should the factory develop? Provide the **remote repository URL** in one of these formats:
|
||||
- Full URL: `https://github.com/johba/harb.git` or `https://codeberg.org/johba/harb.git`
|
||||
- Short slug: `johba/harb` (uses local Forgejo as the primary remote)
|
||||
|
||||
Run the init and watch for:
|
||||
- All bot users created (dev-bot, review-bot, etc.)
|
||||
- `WOODPECKER_TOKEN` generated and saved
|
||||
- Stack containers all started
|
||||
The factory will clone from the remote URL (if provided) or from your local Forgejo, then mirror to the remote.
|
||||
|
||||
Then initialize the factory for that project:
|
||||
```bash
|
||||
bin/disinto init johba/harb --yes
|
||||
# or with full URL:
|
||||
bin/disinto init https://github.com/johba/harb.git --yes
|
||||
```
|
||||
|
||||
The `init` command will:
|
||||
- Create all bot users (dev-bot, review-bot, etc.) on the local Forgejo
|
||||
- Generate and save `WOODPECKER_TOKEN`
|
||||
- Start the stack containers
|
||||
- Clone the target repo into the agent workspace
|
||||
|
||||
> **Note:** The `--repo-root` flag is optional and only needed if you want to customize
|
||||
> where the cloned repo lives. By default, it goes under `/home/agent/repos/<name>`.
|
||||
|
||||
### 3. Post-init verification
|
||||
|
||||
|
|
@ -70,7 +86,48 @@ docker exec disinto-agents-1 chown -R agent:agent /home/agent/repos
|
|||
docker exec -u agent disinto-agents-1 bash -c "source /home/agent/disinto/.env && git clone http://dev-bot:\${FORGE_TOKEN}@forgejo:3000/<org>/<repo>.git /home/agent/repos/<name>"
|
||||
```
|
||||
|
||||
### 4. Mirrors (optional)
|
||||
### 4. Create the project configuration file
|
||||
|
||||
The factory uses a TOML file to configure how it manages your project. Create
|
||||
`projects/<name>.toml` based on the template format:
|
||||
|
||||
```toml
|
||||
# projects/harb.toml
|
||||
|
||||
name = "harb"
|
||||
repo = "johba/harb"
|
||||
forge_url = "http://localhost:3000"
|
||||
repo_root = "/home/agent/repos/harb"
|
||||
primary_branch = "master"
|
||||
|
||||
[ci]
|
||||
woodpecker_repo_id = 0
|
||||
stale_minutes = 60
|
||||
|
||||
[services]
|
||||
containers = ["ponder"]
|
||||
|
||||
[monitoring]
|
||||
check_prs = true
|
||||
check_dev_agent = true
|
||||
check_pipeline_stall = true
|
||||
|
||||
# [mirrors]
|
||||
# github = "git@github.com:johba/harb.git"
|
||||
# codeberg = "git@codeberg.org:johba/harb.git"
|
||||
```
|
||||
|
||||
**Key fields:**
|
||||
- `name`: Project identifier (used for file names, logs, etc.)
|
||||
- `repo`: The source repo in `owner/name` format
|
||||
- `forge_url`: URL of your local Forgejo instance
|
||||
- `repo_root`: Where the agent clones the repo
|
||||
- `primary_branch`: Default branch name (e.g., `main` or `master`)
|
||||
- `woodpecker_repo_id`: Set to `0` initially; auto-populated on first CI run
|
||||
- `containers`: List of Docker containers the factory should manage
|
||||
- `mirrors`: Optional external forge URLs for backup/sync
|
||||
|
||||
### 5. Mirrors (optional)
|
||||
|
||||
[ASK] Should the factory mirror to external forges? If yes, which?
|
||||
- GitHub: need repo URL and SSH key added to GitHub account
|
||||
|
|
@ -88,7 +145,7 @@ ssh -T git@github.com 2>&1; ssh -T git@codeberg.org 2>&1
|
|||
|
||||
If SSH host keys are missing: `ssh-keyscan github.com codeberg.org >> ~/.ssh/known_hosts 2>/dev/null`
|
||||
|
||||
Edit `projects/<name>.toml` to add mirrors:
|
||||
Edit `projects/<name>.toml` to uncomment and configure mirrors:
|
||||
```toml
|
||||
[mirrors]
|
||||
github = "git@github.com:Org/repo.git"
|
||||
|
|
@ -100,7 +157,7 @@ Test with a manual push:
|
|||
source .env && source lib/env.sh && export PROJECT_TOML=projects/<name>.toml && source lib/load-project.sh && source lib/mirrors.sh && mirror_push
|
||||
```
|
||||
|
||||
### 5. Seed the backlog
|
||||
### 6. Seed the backlog
|
||||
|
||||
[ASK] What should the factory work on first? Brainstorm with the user.
|
||||
|
||||
|
|
@ -128,10 +185,12 @@ Use labels:
|
|||
- `blocked` — parked, not for the factory
|
||||
- No label — tracked but not for autonomous work
|
||||
|
||||
### 6. Watch it work
|
||||
### 7. Watch it work
|
||||
|
||||
The dev-agent polls every 5 minutes. Trigger manually to see it immediately:
|
||||
```bash
|
||||
source .env
|
||||
export PROJECT_TOML=projects/<name>.toml
|
||||
docker exec -u agent disinto-agents-1 bash -c "cd /home/agent/disinto && bash dev/dev-poll.sh projects/<name>.toml"
|
||||
```
|
||||
|
||||
|
|
|
|||
22
lib/env.sh
22
lib/env.sh
|
|
@ -29,8 +29,26 @@ if [ -f "$FACTORY_ROOT/.env.enc" ] && command -v sops &>/dev/null; then
|
|||
set -a
|
||||
_saved_forge_url="${FORGE_URL:-}"
|
||||
_saved_forge_token="${FORGE_TOKEN:-}"
|
||||
eval "$(sops -d --output-type dotenv "$FACTORY_ROOT/.env.enc" 2>/dev/null)" \
|
||||
|| echo "Warning: failed to decrypt .env.enc — secrets not loaded" >&2
|
||||
# Use temp file + validate dotenv format before sourcing (avoids eval injection)
|
||||
_tmpenv=$(mktemp) || { echo "Warning: failed to create temp file for .env.enc" >&2; exit 1; }
|
||||
if sops -d --output-type dotenv "$FACTORY_ROOT/.env.enc" > "$_tmpenv" 2>/dev/null; then
|
||||
# Validate: non-empty, non-comment lines must match KEY=value pattern
|
||||
# Filter out blank lines and comments before validation
|
||||
_validated=$(grep -E '^[A-Za-z_][A-Za-z0-9_]*=' "$_tmpenv" 2>/dev/null || true)
|
||||
if [ -n "$_validated" ]; then
|
||||
# Write validated content to a second temp file and source it
|
||||
_validated_env=$(mktemp)
|
||||
printf '%s\n' "$_validated" > "$_validated_env"
|
||||
# shellcheck source=/dev/null
|
||||
source "$_validated_env"
|
||||
rm -f "$_validated_env"
|
||||
else
|
||||
echo "Warning: .env.enc decryption output failed format validation" >&2
|
||||
fi
|
||||
else
|
||||
echo "Warning: failed to decrypt .env.enc — secrets not loaded" >&2
|
||||
fi
|
||||
rm -f "$_tmpenv"
|
||||
set +a
|
||||
[ -n "$_saved_forge_url" ] && export FORGE_URL="$_saved_forge_url"
|
||||
[ -n "$_saved_forge_token" ] && export FORGE_TOKEN="$_saved_forge_token"
|
||||
|
|
|
|||
|
|
@ -45,16 +45,16 @@ _ilc_log() {
|
|||
# Label ID caching — lookup once per name, cache in globals.
|
||||
# Pattern follows ci-helpers.sh (ensure_blocked_label_id).
|
||||
# ---------------------------------------------------------------------------
|
||||
_ILC_BACKLOG_ID=""
|
||||
_ILC_IN_PROGRESS_ID=""
|
||||
_ILC_BLOCKED_ID=""
|
||||
declare -A _ILC_LABEL_IDS
|
||||
_ILC_LABEL_IDS["backlog"]=""
|
||||
_ILC_LABEL_IDS["in-progress"]=""
|
||||
_ILC_LABEL_IDS["blocked"]=""
|
||||
|
||||
# _ilc_ensure_label_id VARNAME LABEL_NAME [COLOR]
|
||||
# Generic: looks up label by name, creates if missing, caches in the named var.
|
||||
# _ilc_ensure_label_id LABEL_NAME [COLOR]
|
||||
# Looks up label by name, creates if missing, caches in associative array.
|
||||
_ilc_ensure_label_id() {
|
||||
local varname="$1" name="$2" color="${3:-#e0e0e0}"
|
||||
local current
|
||||
eval "current=\"\${${varname}:-}\""
|
||||
local name="$1" color="${2:-#e0e0e0}"
|
||||
local current="${_ILC_LABEL_IDS[$name]:-}"
|
||||
if [ -n "$current" ]; then
|
||||
printf '%s' "$current"
|
||||
return 0
|
||||
|
|
@ -71,14 +71,14 @@ _ilc_ensure_label_id() {
|
|||
| jq -r '.id // empty' 2>/dev/null || true)
|
||||
fi
|
||||
if [ -n "$label_id" ]; then
|
||||
eval "${varname}=\"${label_id}\""
|
||||
_ILC_LABEL_IDS["$name"]="$label_id"
|
||||
fi
|
||||
printf '%s' "$label_id"
|
||||
}
|
||||
|
||||
_ilc_backlog_id() { _ilc_ensure_label_id _ILC_BACKLOG_ID "backlog" "#0075ca"; }
|
||||
_ilc_in_progress_id() { _ilc_ensure_label_id _ILC_IN_PROGRESS_ID "in-progress" "#1d76db"; }
|
||||
_ilc_blocked_id() { _ilc_ensure_label_id _ILC_BLOCKED_ID "blocked" "#e11d48"; }
|
||||
_ilc_backlog_id() { _ilc_ensure_label_id "backlog" "#0075ca"; }
|
||||
_ilc_in_progress_id() { _ilc_ensure_label_id "in-progress" "#1d76db"; }
|
||||
_ilc_blocked_id() { _ilc_ensure_label_id "blocked" "#e11d48"; }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# issue_claim — assign issue to bot, add "in-progress" label, remove "backlog".
|
||||
|
|
|
|||
|
|
@ -13,7 +13,16 @@ mirror_push() {
|
|||
|
||||
local name url
|
||||
for name in $MIRROR_NAMES; do
|
||||
url=$(eval "echo \"\$MIRROR_$(echo "$name" | tr '[:lower:]' '[:upper:]')\"") || true
|
||||
# Convert name to uppercase env var name safely (only alphanumeric allowed)
|
||||
local upper_name
|
||||
upper_name=$(printf '%s' "$name" | tr '[:lower:]' '[:upper:]')
|
||||
# Validate: only allow alphanumeric + underscore in var name
|
||||
if [[ ! "$upper_name" =~ ^[A-Z_][A-Z0-9_]*$ ]]; then
|
||||
continue
|
||||
fi
|
||||
# Use indirect expansion safely (no eval) — MIRROR_ prefix required
|
||||
local varname="MIRROR_${upper_name}"
|
||||
url="${!varname:-}"
|
||||
[ -z "$url" ] && continue
|
||||
|
||||
# Ensure remote exists with correct URL
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue