From 90ef03a30437422023a31b5409e0a198c26d5182 Mon Sep 17 00:00:00 2001 From: johba Date: Sat, 14 Mar 2026 13:49:09 +0100 Subject: [PATCH] refactor: make all scripts multi-project via env vars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace hardcoded harb references across the entire codebase: - HARB_REPO_ROOT → PROJECT_REPO_ROOT (with deprecated alias) - Derive PROJECT_NAME from CODEBERG_REPO slug - Add PRIMARY_BRANCH (master/main), WOODPECKER_REPO_ID env vars - Parameterize worktree prefixes, docker container names, branch refs - Genericize agent prompts (gardener, factory supervisor) - Update best-practices docs to use $-vars, prefix harb lessons All project-specific values now flow from .env → lib/env.sh → scripts. Backward-compatible: existing harb setups work without .env changes. Co-Authored-By: Claude Opus 4.6 (1M context) --- .env.example | 23 ++++++++-------- dev/dev-agent.sh | 42 ++++++++++++++--------------- dev/dev-poll.sh | 2 +- factory/PROMPT.md | 8 +++--- factory/best-practices/ci.md | 12 ++++----- factory/best-practices/codeberg.md | 4 +-- factory/best-practices/dev-agent.md | 6 ++--- factory/best-practices/disk.md | 8 +++--- factory/best-practices/git.md | 30 ++++++++++----------- factory/best-practices/memory.md | 6 ++--- factory/factory-poll.sh | 29 ++++++++++---------- gardener/gardener-poll.sh | 23 +++++++--------- lib/ci-debug.sh | 5 ++-- lib/env.sh | 8 ++++-- review/review-poll.sh | 9 +++---- review/review-pr.sh | 18 ++++++------- 16 files changed, 117 insertions(+), 116 deletions(-) diff --git a/.env.example b/.env.example index 9b24b87..8c911fe 100644 --- a/.env.example +++ b/.env.example @@ -2,17 +2,23 @@ # Copy to .env and fill in your values. # NEVER commit .env to the repo. +# ── Target project ──────────────────────────────────────────────────────── +CODEBERG_REPO=johba/yourproject # org/repo slug on Codeberg +PROJECT_REPO_ROOT=/home/you/yourproject # local clone of the target repo +PRIMARY_BRANCH=main # main or master +# PROJECT_NAME=yourproject # optional — auto-derived from CODEBERG_REPO + +# ── Auth tokens ─────────────────────────────────────────────────────────── # Codeberg API token (read from ~/.netrc by default, override here if needed) # CODEBERG_TOKEN= # Codeberg review bot token (separate account for formal reviews) REVIEW_BOT_TOKEN= -# Woodpecker CI API token +# ── Woodpecker CI ───────────────────────────────────────────────────────── WOODPECKER_TOKEN= - -# Woodpecker CI server URL WOODPECKER_SERVER=http://localhost:8000 +WOODPECKER_REPO_ID=2 # numeric repo ID in Woodpecker DB # Woodpecker Postgres (for direct DB queries) WOODPECKER_DB_PASSWORD= @@ -20,12 +26,5 @@ WOODPECKER_DB_USER=woodpecker WOODPECKER_DB_HOST=127.0.0.1 WOODPECKER_DB_NAME=woodpecker -# Target Codeberg repo -CODEBERG_REPO=johba/harb -CODEBERG_API=https://codeberg.org/api/v1/repos/johba/harb - -# Harb repo local path -HARB_REPO_ROOT=/home/debian/harb - -# Claude CLI timeout (seconds) -CLAUDE_TIMEOUT=7200 +# ── Tuning ──────────────────────────────────────────────────────────────── +CLAUDE_TIMEOUT=7200 # max seconds per Claude invocation diff --git a/dev/dev-agent.sh b/dev/dev-agent.sh index 5bfe2a9..ba61dad 100755 --- a/dev/dev-agent.sh +++ b/dev/dev-agent.sh @@ -20,7 +20,7 @@ # {"status": "already_done", "reason": "..."} # # Peek: cat /tmp/dev-agent-status -# Log: tail -f ~/scripts/harb-dev/dev-agent.log +# Log: tail -f dev-agent.log set -euo pipefail @@ -31,7 +31,7 @@ source "$(dirname "$0")/../lib/env.sh" # --- Config --- ISSUE="${1:?Usage: dev-agent.sh }" REPO="${CODEBERG_REPO}" -REPO_ROOT="${HARB_REPO_ROOT}" +REPO_ROOT="${PROJECT_REPO_ROOT}" API="${CODEBERG_API}" LOCKFILE="/tmp/dev-agent.lock" @@ -39,7 +39,7 @@ STATUSFILE="/tmp/dev-agent-status" LOGFILE="${FACTORY_ROOT}/dev/dev-agent.log" PREFLIGHT_RESULT="/tmp/dev-agent-preflight.json" BRANCH="fix/issue-${ISSUE}" -WORKTREE="/tmp/harb-worktree-${ISSUE}" +WORKTREE="/tmp/${PROJECT_NAME}-worktree-${ISSUE}" REVIEW_POLL_INTERVAL=300 # 5 min between review checks MAX_REVIEW_ROUNDS=5 CLAUDE_TIMEOUT=7200 @@ -101,7 +101,7 @@ write_state_entry() { description=$(echo "$ISSUE_TITLE" | sed 's/^feat:\s*//i;s/^fix:\s*//i;s/^refactor:\s*//i') local line="- [${today}] ${description} (#${ISSUE})" if [ ! -f "$state_file" ]; then - printf '# STATE.md — What harb currently is and does\n\n' > "$state_file" + printf '# STATE.md — What %s currently is and does\n\n' "${PROJECT_NAME}" > "$state_file" fi echo "$line" >> "$state_file" log "STATE.md: ${line}" @@ -425,7 +425,7 @@ if [ -n "$EXISTING_PR" ]; then fi REVIEW_PROMPT="You are working in a git worktree at ${WORKTREE} on branch ${BRANCH}. -This is issue #${ISSUE} for the harb DeFi protocol. +This is issue #${ISSUE} for the ${CODEBERG_REPO} project. ## Issue: ${ISSUE_TITLE} @@ -539,28 +539,28 @@ else status "creating worktree" cd "$REPO_ROOT" - # Ensure repo is in clean state (abort stale rebases, checkout master) + # Ensure repo is in clean state (abort stale rebases, checkout primary branch) if [ -d "$REPO_ROOT/.git/rebase-merge" ] || [ -d "$REPO_ROOT/.git/rebase-apply" ]; then log "WARNING: stale rebase detected in main repo — aborting" git rebase --abort 2>/dev/null || true fi CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown") - if [ "$CURRENT_BRANCH" != "master" ]; then - log "WARNING: main repo on '$CURRENT_BRANCH' instead of master — switching" - git checkout master 2>/dev/null || true + if [ "$CURRENT_BRANCH" != "${PRIMARY_BRANCH}" ]; then + log "WARNING: main repo on '$CURRENT_BRANCH' instead of ${PRIMARY_BRANCH} — switching" + git checkout "${PRIMARY_BRANCH}" 2>/dev/null || true fi - git fetch origin master 2>/dev/null - git pull --ff-only origin master 2>/dev/null || true + git fetch origin "${PRIMARY_BRANCH}" 2>/dev/null + git pull --ff-only origin "${PRIMARY_BRANCH}" 2>/dev/null || true cleanup_worktree - git worktree add "$WORKTREE" origin/master -B "$BRANCH" 2>&1 || { + git worktree add "$WORKTREE" "origin/${PRIMARY_BRANCH}" -B "$BRANCH" 2>&1 || { log "ERROR: worktree creation failed" - git worktree add "$WORKTREE" origin/master -B "$BRANCH" 2>&1 | while read -r wt_line; do log " $wt_line"; done || true + git worktree add "$WORKTREE" "origin/${PRIMARY_BRANCH}" -B "$BRANCH" 2>&1 | while read -r wt_line; do log " $wt_line"; done || true cleanup_labels exit 1 } cd "$WORKTREE" - git checkout -B "$BRANCH" origin/master 2>/dev/null + git checkout -B "$BRANCH" "origin/${PRIMARY_BRANCH}" 2>/dev/null git submodule update --init --recursive 2>/dev/null || true # Write STATE.md entry — included in the first commit, reads as done once PR merges @@ -581,7 +581,7 @@ else jq -r '.[] | "#\(.number) \(.title)"' 2>/dev/null || echo "(could not fetch)") PROMPT="You are working in a git worktree at ${WORKTREE} on branch ${BRANCH}. -You have been assigned issue #${ISSUE} for the harb DeFi protocol. +You have been assigned issue #${ISSUE} for the ${CODEBERG_REPO} project. ## Issue: ${ISSUE_TITLE} @@ -680,7 +680,7 @@ If you cannot or should not implement this issue, output ONLY a JSON object (no # But only treat as refusal if there are NO commits (claude might output JSON-like text AND commit) cd "$WORKTREE" - AHEAD=$(git rev-list origin/master..HEAD --count 2>/dev/null || echo "0") + AHEAD=$(git rev-list "origin/${PRIMARY_BRANCH}..HEAD" --count 2>/dev/null || echo "0") HAS_CHANGES=$(git status --porcelain) if [ -n "$REFUSAL_JSON" ] && [ "$AHEAD" -eq 0 ] && [ -z "$HAS_CHANGES" ]; then @@ -839,7 +839,7 @@ $(printf '%s' "$REFUSAL_JSON" | head -c 2000) --arg title "fix: ${ISSUE_TITLE} (#${ISSUE})" \ --rawfile body "/tmp/pr-body-${ISSUE}.txt" \ --arg head "$BRANCH" \ - --arg base "master" \ + --arg base "${PRIMARY_BRANCH}" \ '{title: $title, body: $body, head: $head, base: $base}' > /tmp/pr-request-${ISSUE}.json PR_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ @@ -894,7 +894,7 @@ do_merge() { if [ "$mergeable" = "false" ]; then log "PR #${PR_NUMBER} has merge conflicts — attempting rebase" local work_dir="${WORKTREE:-$REPO_ROOT}" - if (cd "$work_dir" && git fetch origin master && git rebase origin/master 2>&1); then + if (cd "$work_dir" && git fetch origin "${PRIMARY_BRANCH}" && git rebase "origin/${PRIMARY_BRANCH}" 2>&1); then log "rebase succeeded — force pushing" (cd "$work_dir" && git push origin "${BRANCH}" --force-with-lease 2>&1) || true # Wait for CI on the new commit @@ -925,8 +925,8 @@ do_merge() { if [ "$http_code" = "200" ] || [ "$http_code" = "204" ]; then log "PR #${PR_NUMBER} merged!" - # Update STATE.md on master (pull merged changes first) - (cd "$REPO_ROOT" && git checkout master 2>/dev/null && git pull --ff-only origin master 2>/dev/null) || true + # Update STATE.md on primary branch (pull merged changes first) + (cd "$REPO_ROOT" && git checkout "${PRIMARY_BRANCH}" 2>/dev/null && git pull --ff-only origin "${PRIMARY_BRANCH}" 2>/dev/null) || true append_state_log || log "WARNING: STATE.md update failed (non-fatal)" curl -sf -X DELETE \ @@ -995,7 +995,7 @@ while [ "$REVIEW_ROUND" -lt "$MAX_REVIEW_ROUNDS" ]; do if [ -n "$PIPELINE_NUM" ]; then FAILED_INFO=$(curl -sf \ -H "Authorization: Bearer ${WOODPECKER_TOKEN}" \ - "http://localhost:8000/api/repos/2/pipelines/${PIPELINE_NUM}" | \ + "${WOODPECKER_SERVER}/api/repos/${WOODPECKER_REPO_ID}/pipelines/${PIPELINE_NUM}" | \ jq -r '.workflows[]?.children[]? | select(.state=="failure") | "\(.name)|\(.exit_code)"' | head -1) FAILED_STEP=$(echo "$FAILED_INFO" | cut -d'|' -f1) FAILED_EXIT=$(echo "$FAILED_INFO" | cut -d'|' -f2) diff --git a/dev/dev-poll.sh b/dev/dev-poll.sh index c7f701f..4a14be2 100755 --- a/dev/dev-poll.sh +++ b/dev/dev-poll.sh @@ -2,7 +2,7 @@ # dev-poll.sh — Pull-based factory: find the next ready issue and start dev-agent # # Pull system: issues labeled "backlog" are candidates. An issue is READY when -# ALL its dependency issues are closed AND their PRs are merged into master. +# ALL its dependency issues are closed (and their PRs merged). # No "todo" label needed — readiness is derived from reality. # # Priority: diff --git a/factory/PROMPT.md b/factory/PROMPT.md index 4dad996..96aac64 100644 --- a/factory/PROMPT.md +++ b/factory/PROMPT.md @@ -1,6 +1,6 @@ # Factory Supervisor -You are the factory supervisor for `johba/harb`. You were called because +You are the factory supervisor for `$CODEBERG_REPO`. You were called because `factory-poll.sh` detected an issue it couldn't auto-fix. ## Priority Order @@ -34,9 +34,11 @@ source ${FACTORY_ROOT}/lib/env.sh This gives you: - `codeberg_api GET "/pulls?state=open"` — Codeberg API (uses $CODEBERG_TOKEN) - `wpdb -c "SELECT ..."` — Woodpecker Postgres (uses $WOODPECKER_DB_PASSWORD) -- `woodpecker_api "/repos/2/pipelines"` — Woodpecker REST API (uses $WOODPECKER_TOKEN) +- `woodpecker_api "/repos/$WOODPECKER_REPO_ID/pipelines"` — Woodpecker REST API (uses $WOODPECKER_TOKEN) - `$REVIEW_BOT_TOKEN` — for posting reviews as the review_bot account -- `$HARB_REPO_ROOT` — path to the harb repo +- `$PROJECT_REPO_ROOT` — path to the target project repo +- `$PROJECT_NAME` — short project name (for worktree prefixes, container names) +- `$PRIMARY_BRANCH` — main branch (master or main) - `$FACTORY_ROOT` — path to the dark-factory repo ## Escalation diff --git a/factory/best-practices/ci.md b/factory/best-practices/ci.md index 9ae1e83..5bafa86 100644 --- a/factory/best-practices/ci.md +++ b/factory/best-practices/ci.md @@ -4,15 +4,15 @@ - Woodpecker CI at localhost:8000 (Docker backend) - Postgres DB: use `wpdb` helper from env.sh - Woodpecker API: use `woodpecker_api` helper from env.sh -- CI images: pre-built at `registry.niovi.voyage/harb/*:latest` +- Example (harb): CI images pre-built at `registry.niovi.voyage/harb/*:latest` ## Safe Fixes - Retrigger CI: push empty commit to PR branch ```bash - cd /tmp/harb-worktree- && git commit --allow-empty -m "ci: retrigger" --no-verify && git push origin --force + cd /tmp/${PROJECT_NAME}-worktree- && git commit --allow-empty -m "ci: retrigger" --no-verify && git push origin --force ``` - Restart woodpecker-agent: `sudo systemctl restart woodpecker-agent` -- View pipeline status: `wpdb -c "SELECT number, status FROM pipelines WHERE repo_id=2 ORDER BY number DESC LIMIT 5;"` +- View pipeline status: `wpdb -c "SELECT number, status FROM pipelines WHERE repo_id=$WOODPECKER_REPO_ID ORDER BY number DESC LIMIT 5;"` - View failed steps: `bash ${FACTORY_ROOT}/lib/ci-debug.sh failures ` - View step logs: `bash ${FACTORY_ROOT}/lib/ci-debug.sh logs ` @@ -23,7 +23,7 @@ ## Known Issues - Codeberg rate-limits SSH clones. `git` step fails with exit 128. Retrigger usually works. - `log_entries` table grows fast (was 5.6GB once). Truncate periodically. -- Running CI + harb stack = 14+ containers on 8GB. Memory pressure is real. +- Example (harb): Running CI + harb stack = 14+ containers on 8GB. Memory pressure is real. - CI images take hours to rebuild. Never run `docker system prune -a`. ## Lessons Learned @@ -31,10 +31,10 @@ - Exit code 137 = OOM kill. Check memory, kill stale processes, retrigger. - `node-quality` step fails on eslint/typescript errors — these need code fixes, not CI fixes. -### FEE_DEST address must match DeployLocal.sol +### Example (harb): FEE_DEST address must match DeployLocal.sol When DeployLocal.sol changes the feeDest address, bootstrap-common.sh must also be updated. Current feeDest = keccak256('harb.local.feeDest') = 0x8A9145E1Ea4C4d7FB08cF1011c8ac1F0e10F9383. Symptom: bootstrap step exits 1 after 'Granting recenter access to deployer' with no error — setRecenterAccess reverts because wrong address is impersonated. -### keccak-derived FEE_DEST requires anvil_setBalance before impersonation +### Example (harb): keccak-derived FEE_DEST requires anvil_setBalance before impersonation When FEE_DEST is a keccak-derived address (e.g. keccak256('harb.local.feeDest')), it has zero ETH balance. Any function that calls `anvil_impersonateAccount` then `cast send --from $FEE_DEST --unlocked` will fail silently (output redirected to LOG_FILE) but exit 1 due to gas deduction failure. Fix: add `cast rpc anvil_setBalance "$FEE_DEST" "0xDE0B6B3A7640000"` before impersonation. Applied in both bootstrap-common.sh and red-team.sh. diff --git a/factory/best-practices/codeberg.md b/factory/best-practices/codeberg.md index 714c409..c8beaca 100644 --- a/factory/best-practices/codeberg.md +++ b/factory/best-practices/codeberg.md @@ -10,8 +10,8 @@ Codeberg rate-limits SSH and HTTPS clones. Symptoms: - **Do NOT retrigger** during a rate-limit storm. Wait 10-15 minutes. - Check if multiple pipelines failed on `git` step recently: ```bash - wpdb -c "SELECT number, status, to_timestamp(started) FROM pipelines WHERE repo_id=2 AND status='failure' ORDER BY number DESC LIMIT 5;" - wpdb -c "SELECT s.name, s.exit_code FROM steps s JOIN pipelines p ON s.pipeline_id=p.id WHERE p.number= AND p.repo_id=2 AND s.state='failure';" + wpdb -c "SELECT number, status, to_timestamp(started) FROM pipelines WHERE repo_id=$WOODPECKER_REPO_ID AND status='failure' ORDER BY number DESC LIMIT 5;" + wpdb -c "SELECT s.name, s.exit_code FROM steps s JOIN pipelines p ON s.pipeline_id=p.id WHERE p.number= AND p.repo_id=$WOODPECKER_REPO_ID AND s.state='failure';" ``` - If multiple `git` failures with exit 128 in the last 15 min → it's rate limiting. Wait. - Only retrigger after 15+ minutes of no CI activity. diff --git a/factory/best-practices/dev-agent.md b/factory/best-practices/dev-agent.md index 99bb9da..e8a15f2 100644 --- a/factory/best-practices/dev-agent.md +++ b/factory/best-practices/dev-agent.md @@ -5,13 +5,13 @@ - `dev-agent.sh` uses `claude -p` for implementation, runs in git worktree - Lock file: `/tmp/dev-agent.lock` (contains PID) - Status file: `/tmp/dev-agent-status` -- Worktrees: `/tmp/harb-worktree-/` +- Worktrees: `/tmp/${PROJECT_NAME}-worktree-/` ## Safe Fixes - Remove stale lock: `rm -f /tmp/dev-agent.lock` (only if PID is dead) - Kill stuck agent: `kill ` then clean lock - Restart on derailed PR: `bash ${FACTORY_ROOT}/dev/dev-agent.sh &` -- Clean worktree: `cd /home/debian/harb && git worktree remove /tmp/harb-worktree- --force` +- Clean worktree: `cd $PROJECT_REPO_ROOT && git worktree remove /tmp/${PROJECT_NAME}-worktree- --force` - Remove `in-progress` label if agent died without cleanup: ```bash codeberg_api DELETE "/issues//labels/in-progress" @@ -38,7 +38,7 @@ ## Dependency Resolution -**Trust closed state.** If a dependency issue is closed, the code is on master. Period. +**Trust closed state.** If a dependency issue is closed, the code is on the primary branch. Period. DO NOT try to find the specific PR that closed an issue. This is over-engineering that causes false negatives: - Codeberg shares issue/PR numbering — no guaranteed relationship diff --git a/factory/best-practices/disk.md b/factory/best-practices/disk.md index 4d2a411..6b63484 100644 --- a/factory/best-practices/disk.md +++ b/factory/best-practices/disk.md @@ -3,14 +3,14 @@ ## Safe Fixes - Docker cleanup: `sudo docker system prune -f` (keeps images, removes stopped containers + dangling layers) - Truncate factory logs >5MB: `truncate -s 0 ` -- Remove stale worktrees: check `/tmp/harb-worktree-*`, only if dev-agent not running on them +- Remove stale worktrees: check `/tmp/${PROJECT_NAME}-worktree-*`, only if dev-agent not running on them - Woodpecker log_entries: `DELETE FROM log_entries WHERE id < (SELECT max(id) - 100000 FROM log_entries);` then `VACUUM;` -- Node module caches in worktrees: `rm -rf /tmp/harb-worktree-*/node_modules/` -- Git garbage collection: `cd /home/debian/harb && git gc --prune=now` +- Node module caches in worktrees: `rm -rf /tmp/${PROJECT_NAME}-worktree-*/node_modules/` +- Git garbage collection: `cd $PROJECT_REPO_ROOT && git gc --prune=now` ## Dangerous (escalate) - `docker system prune -a --volumes` — deletes ALL images including CI build cache -- Deleting anything in `/home/debian/harb/` that's tracked by git +- Deleting anything in `$PROJECT_REPO_ROOT/` that's tracked by git - Truncating Woodpecker DB tables other than log_entries ## Known Disk Hogs diff --git a/factory/best-practices/git.md b/factory/best-practices/git.md index 736e0fd..a48045c 100644 --- a/factory/best-practices/git.md +++ b/factory/best-practices/git.md @@ -1,39 +1,39 @@ # Git Best Practices ## Environment -- Repo: `/home/debian/harb`, remote: `codeberg.org/johba/harb` -- Branch: `master` (protected — no direct push, PRs only) -- Worktrees: `/tmp/harb-worktree-/` +- Repo: `$PROJECT_REPO_ROOT`, remote: `$PROJECT_REMOTE` +- Branch: `$PRIMARY_BRANCH` (protected — no direct push, PRs only) +- Worktrees: `/tmp/${PROJECT_NAME}-worktree-/` ## Safe Fixes -- Abort stale rebase: `cd /home/debian/harb && git rebase --abort` -- Switch to master: `git checkout master` +- Abort stale rebase: `cd $PROJECT_REPO_ROOT && git rebase --abort` +- Switch to $PRIMARY_BRANCH: `git checkout $PRIMARY_BRANCH` - Prune worktrees: `git worktree prune` - Reset dirty state: `git checkout -- .` (only uncommitted changes) -- Fetch latest: `git fetch origin master` +- Fetch latest: `git fetch origin $PRIMARY_BRANCH` ## Auto-fixable by Supervisor -- **Merge conflict on approved PR**: rebase onto master and force-push +- **Merge conflict on approved PR**: rebase onto $PRIMARY_BRANCH and force-push ```bash - cd /tmp/harb-worktree- || git worktree add /tmp/harb-worktree- - cd /tmp/harb-worktree- - git fetch origin master - git rebase origin/master + cd /tmp/${PROJECT_NAME}-worktree- || git worktree add /tmp/${PROJECT_NAME}-worktree- + cd /tmp/${PROJECT_NAME}-worktree- + git fetch origin $PRIMARY_BRANCH + git rebase origin/$PRIMARY_BRANCH # If conflict is trivial (NatSpec, comments): resolve and continue # If conflict is code logic: escalate to Clawy git push origin --force ``` -- **Stale rebase**: `git rebase --abort && git checkout master` -- **Wrong branch**: `git checkout master` +- **Stale rebase**: `git rebase --abort && git checkout $PRIMARY_BRANCH` +- **Wrong branch**: `git checkout $PRIMARY_BRANCH` ## Dangerous (escalate) - `git reset --hard` on any branch with unpushed work - Deleting remote branches - Force-pushing to any branch -- Anything on the master branch directly +- Anything on the $PRIMARY_BRANCH branch directly ## Known Issues -- Main repo MUST be on master at all times. Dev work happens in worktrees. +- Main repo MUST be on $PRIMARY_BRANCH at all times. Dev work happens in worktrees. - Stale rebases (detached HEAD) break all worktree creation — silent factory stall. - `git worktree add` fails if target directory exists (even empty). Remove first. - Many old branches exist locally (100+). Normal — don't bulk-delete. diff --git a/factory/best-practices/memory.md b/factory/best-practices/memory.md index 1954b0f..fb6c3ce 100644 --- a/factory/best-practices/memory.md +++ b/factory/best-practices/memory.md @@ -7,12 +7,12 @@ ## Safe Fixes (no permission needed) - Kill stale `claude` processes (>3h old): `pgrep -f "claude" --older 10800 | xargs kill` - Drop filesystem caches: `sync && echo 3 | sudo tee /proc/sys/vm/drop_caches` -- Restart bloated Anvil: `sudo docker restart harb-anvil-1` (grows to 12GB+ over hours) +- Restart bloated Anvil: `sudo docker restart ${PROJECT_NAME}-anvil-1` (grows to 12GB+ over hours) - Kill orphan node processes from dead worktrees ## Dangerous (escalate) - `docker system prune -a --volumes` — kills CI images, hours to rebuild -- Stopping harb stack containers — breaks dev environment +- Stopping project stack containers — breaks dev environment - OOM that survives all safe fixes — needs human decision on what to kill ## Known Memory Hogs @@ -26,4 +26,4 @@ ## Lessons Learned - After killing processes, always `sync && echo 3 | sudo tee /proc/sys/vm/drop_caches` - Swap doesn't drain from dropping caches alone — it's actual paged-out process memory -- Running CI + full harb stack = 14+ containers on 8GB. Only one pipeline at a time. +- Running CI + full project stack = 14+ containers on 8GB. Only one pipeline at a time. diff --git a/factory/factory-poll.sh b/factory/factory-poll.sh index 4627039..f807a18 100755 --- a/factory/factory-poll.sh +++ b/factory/factory-poll.sh @@ -75,9 +75,10 @@ if [ "${AVAIL_MB:-9999}" -lt 500 ] || { [ "${SWAP_USED_MB:-0}" -gt 3000 ] && [ " fixed "Dropped filesystem caches" # Restart Anvil if it's bloated (>1GB RSS) - ANVIL_RSS=$(sudo docker stats harb-anvil-1 --no-stream --format '{{.MemUsage}}' 2>/dev/null | grep -oP '^\S+' | head -1 || echo "0") + ANVIL_CONTAINER="${ANVIL_CONTAINER:-${PROJECT_NAME}-anvil-1}" + ANVIL_RSS=$(sudo docker stats "$ANVIL_CONTAINER" --no-stream --format '{{.MemUsage}}' 2>/dev/null | grep -oP '^\S+' | head -1 || echo "0") if echo "$ANVIL_RSS" | grep -qP '\dGiB'; then - sudo docker restart harb-anvil-1 >/dev/null 2>&1 && fixed "Restarted bloated Anvil (${ANVIL_RSS})" + sudo docker restart "$ANVIL_CONTAINER" >/dev/null 2>&1 && fixed "Restarted bloated Anvil (${ANVIL_RSS})" fi # Re-check after fixes @@ -116,12 +117,12 @@ if [ "${DISK_PERCENT:-0}" -gt 80 ]; then done # Clean old worktrees - IDLE_WORKTREES=$(find /tmp/harb-worktree-* -maxdepth 0 -mmin +360 2>/dev/null || true) + IDLE_WORKTREES=$(find /tmp/${PROJECT_NAME}-worktree-* -maxdepth 0 -mmin +360 2>/dev/null || true) if [ -n "$IDLE_WORKTREES" ]; then - cd "${HARB_REPO_ROOT}" && git worktree prune 2>/dev/null + cd "${PROJECT_REPO_ROOT}" && git worktree prune 2>/dev/null for wt in $IDLE_WORKTREES; do # Only remove if dev-agent is not running on it - ISSUE_NUM=$(basename "$wt" | sed 's/harb-worktree-//') + ISSUE_NUM=$(basename "$wt" | sed "s/${PROJECT_NAME}-worktree-//") if ! pgrep -f "dev-agent.sh ${ISSUE_NUM}" >/dev/null 2>&1; then rm -rf "$wt" && fixed "Removed stale worktree: $wt" fi @@ -153,10 +154,10 @@ fi status "P2: checking factory" # CI stuck -STUCK_CI=$(wpdb -c "SELECT count(*) FROM pipelines WHERE repo_id=2 AND status='running' AND EXTRACT(EPOCH FROM now() - to_timestamp(started)) > 1200;" 2>/dev/null | xargs) +STUCK_CI=$(wpdb -c "SELECT count(*) FROM pipelines WHERE repo_id=${WOODPECKER_REPO_ID} AND status='running' AND EXTRACT(EPOCH FROM now() - to_timestamp(started)) > 1200;" 2>/dev/null | xargs) [ "${STUCK_CI:-0}" -gt 0 ] && p2 "CI: ${STUCK_CI} pipeline(s) running >20min" -PENDING_CI=$(wpdb -c "SELECT count(*) FROM pipelines WHERE repo_id=2 AND status='pending' AND EXTRACT(EPOCH FROM now() - to_timestamp(created)) > 1800;" 2>/dev/null | xargs) +PENDING_CI=$(wpdb -c "SELECT count(*) FROM pipelines WHERE repo_id=${WOODPECKER_REPO_ID} AND status='pending' AND EXTRACT(EPOCH FROM now() - to_timestamp(created)) > 1800;" 2>/dev/null | xargs) [ "${PENDING_CI:-0}" -gt 0 ] && p2 "CI: ${PENDING_CI} pipeline(s) pending >30min" # Dev-agent health @@ -177,19 +178,19 @@ if [ -f "$DEV_LOCK" ]; then fi # Git repo health -cd "${HARB_REPO_ROOT}" 2>/dev/null || true +cd "${PROJECT_REPO_ROOT}" 2>/dev/null || true GIT_BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") GIT_REBASE=$([ -d .git/rebase-merge ] || [ -d .git/rebase-apply ] && echo "yes" || echo "no") if [ "$GIT_REBASE" = "yes" ]; then - git rebase --abort 2>/dev/null && git checkout master 2>/dev/null && \ - fixed "Aborted stale rebase, switched to master" || \ + git rebase --abort 2>/dev/null && git checkout "${PRIMARY_BRANCH}" 2>/dev/null && \ + fixed "Aborted stale rebase, switched to ${PRIMARY_BRANCH}" || \ p2 "Git: stale rebase, auto-abort failed" fi -if [ "$GIT_BRANCH" != "master" ] && [ "$GIT_BRANCH" != "unknown" ]; then - git checkout master 2>/dev/null && \ - fixed "Switched main repo from '${GIT_BRANCH}' to master" || \ - p2 "Git: on '${GIT_BRANCH}' instead of master" +if [ "$GIT_BRANCH" != "${PRIMARY_BRANCH}" ] && [ "$GIT_BRANCH" != "unknown" ]; then + git checkout "${PRIMARY_BRANCH}" 2>/dev/null && \ + fixed "Switched main repo from '${GIT_BRANCH}' to ${PRIMARY_BRANCH}" || \ + p2 "Git: on '${GIT_BRANCH}' instead of ${PRIMARY_BRANCH}" fi # ============================================================================= diff --git a/gardener/gardener-poll.sh b/gardener/gardener-poll.sh index 9bb7586..41bb35a 100755 --- a/gardener/gardener-poll.sh +++ b/gardener/gardener-poll.sh @@ -151,7 +151,7 @@ log "Invoking claude -p for grooming" # Build issue summary for context (titles + labels + deps) ISSUE_SUMMARY=$(echo "$ISSUES_JSON" | jq -r '.[] | "#\(.number) [\(.labels | map(.name) | join(","))] \(.title)"') -PROMPT="You are harb's issue gardener. Your job: keep the backlog clean, well-structured, and actionable. +PROMPT="You are the issue gardener for ${CODEBERG_REPO}. Your job: keep the backlog clean, well-structured, and actionable. ## Current open issues $ISSUE_SUMMARY @@ -161,18 +161,15 @@ $(echo -e "$PROBLEMS") ## Tools available - Codeberg API: use curl with the CODEBERG_TOKEN env var (already set in your environment) -- Base URL: https://codeberg.org/api/v1/repos/johba/harb -- Read issue: \`curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" 'https://codeberg.org/api/v1/repos/johba/harb/issues/{number}' | jq '.body'\` -- Relabel: \`curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X PUT -H 'Content-Type: application/json' 'https://codeberg.org/api/v1/repos/johba/harb/issues/{number}/labels' -d '{\"labels\":[652336]}'\` (652336=backlog, 1219499=tech-debt) -- Comment: \`curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X POST -H 'Content-Type: application/json' 'https://codeberg.org/api/v1/repos/johba/harb/issues/{number}/comments' -d '{\"body\":\"...\"}'\` -- Close: \`curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X PATCH -H 'Content-Type: application/json' 'https://codeberg.org/api/v1/repos/johba/harb/issues/{number}' -d '{\"state\":\"closed\"}'\` -- Edit body: \`curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X PATCH -H 'Content-Type: application/json' 'https://codeberg.org/api/v1/repos/johba/harb/issues/{number}' -d '{\"body\":\"new body\"}'\` +- Base URL: ${CODEBERG_API} +- Read issue: \`curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" '${CODEBERG_API}/issues/{number}' | jq '.body'\` +- Relabel: \`curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X PUT -H 'Content-Type: application/json' '${CODEBERG_API}/issues/{number}/labels' -d '{\"labels\":[LABEL_ID]}'\` +- Comment: \`curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X POST -H 'Content-Type: application/json' '${CODEBERG_API}/issues/{number}/comments' -d '{\"body\":\"...\"}'\` +- Close: \`curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X PATCH -H 'Content-Type: application/json' '${CODEBERG_API}/issues/{number}' -d '{\"state\":\"closed\"}'\` +- Edit body: \`curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X PATCH -H 'Content-Type: application/json' '${CODEBERG_API}/issues/{number}' -d '{\"body\":\"new body\"}'\` +- List labels: \`curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" '${CODEBERG_API}/labels'\` (to find label IDs) - NEVER echo, log, or include the actual token value in any output — always reference \$CODEBERG_TOKEN -- You're running in the harb repo root. Read these before making decisions: - - docs/PRODUCT-TRUTH.md — what the protocol is, key mechanics - - docs/ARCHITECTURE.md — file structure, packages, how things connect - - AGENTS.md — repo conventions, dev-agent expectations - - tools/push3-transpiler/README.md — Push3 instruction set (for seed issues) +- You're running in the project repo root. Read README.md and any docs/ files before making decisions. ## Primary mission: promote tech-debt → backlog Most open issues are raw review-bot findings labeled \`tech-debt\`. Your main job is to convert them into well-structured \`backlog\` items the dev-agent can execute. For each tech-debt issue: @@ -212,7 +209,7 @@ ESCALATE - If an issue is ambiguous or needs a design decision, ESCALATE it — don't skip it silently. - Every tech-debt issue in the list above should result in either an ACTION (promoted) or an ESCALATE (needs decision). Never skip silently." -CLAUDE_OUTPUT=$(cd /home/debian/harb && CODEBERG_TOKEN="$CODEBERG_TOKEN" timeout "$CLAUDE_TIMEOUT" \ +CLAUDE_OUTPUT=$(cd "${PROJECT_REPO_ROOT}" && CODEBERG_TOKEN="$CODEBERG_TOKEN" timeout "$CLAUDE_TIMEOUT" \ claude -p "$PROMPT" \ --model sonnet \ --dangerously-skip-permissions \ diff --git a/lib/ci-debug.sh b/lib/ci-debug.sh index aef04bd..e1b10f3 100755 --- a/lib/ci-debug.sh +++ b/lib/ci-debug.sh @@ -12,10 +12,9 @@ set -euo pipefail # Load shared environment source "$(dirname "$0")/../lib/env.sh" -export WOODPECKER_SERVER="http://localhost:8000" # WOODPECKER_TOKEN loaded from .env via env.sh -REPO="johba/harb" -API="${WOODPECKER_SERVER}/api/repos/2" +REPO="${CODEBERG_REPO}" +API="${WOODPECKER_SERVER}/api/repos/${WOODPECKER_REPO_ID}" api() { curl -sf -H "Authorization: Bearer ${WOODPECKER_TOKEN}" "${API}/$1" diff --git a/lib/env.sh b/lib/env.sh index d53e01d..7f3018a 100755 --- a/lib/env.sh +++ b/lib/env.sh @@ -25,10 +25,14 @@ if [ -z "${CODEBERG_TOKEN:-}" ]; then fi export CODEBERG_TOKEN -# Defaults +# Project config export CODEBERG_REPO="${CODEBERG_REPO:-johba/harb}" export CODEBERG_API="${CODEBERG_API:-https://codeberg.org/api/v1/repos/${CODEBERG_REPO}}" -export HARB_REPO_ROOT="${HARB_REPO_ROOT:-/home/debian/harb}" +export PROJECT_NAME="${PROJECT_NAME:-${CODEBERG_REPO##*/}}" +export PROJECT_REPO_ROOT="${PROJECT_REPO_ROOT:-${HARB_REPO_ROOT:-/home/${USER}/${PROJECT_NAME}}}" +export HARB_REPO_ROOT="${PROJECT_REPO_ROOT}" # deprecated alias +export PRIMARY_BRANCH="${PRIMARY_BRANCH:-master}" +export WOODPECKER_REPO_ID="${WOODPECKER_REPO_ID:-2}" export WOODPECKER_SERVER="${WOODPECKER_SERVER:-http://localhost:8000}" export CLAUDE_TIMEOUT="${CLAUDE_TIMEOUT:-7200}" diff --git a/review/review-poll.sh b/review/review-poll.sh index 9fd9aa3..2fd9240 100755 --- a/review/review-poll.sh +++ b/review/review-poll.sh @@ -1,15 +1,14 @@ #!/usr/bin/env bash # review-poll.sh — Poll open PRs and review those with green CI # -# Peek while running: cat /tmp/harb-review-status -# Full log: tail -f ~/scripts/harb-review/review.log +# Peek while running: cat /tmp/-review-status +# Full log: tail -f /review/review.log set -euo pipefail # Load shared environment source "$(dirname "$0")/../lib/env.sh" -export HOME="${HOME:-/home/debian}" REPO="${CODEBERG_REPO}" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" @@ -35,10 +34,10 @@ log "--- Poll start ---" PRS=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ "${API_BASE}/pulls?state=open&limit=20" | \ - jq -r '.[] | select(.base.ref == "master") | select(.draft != true) | select(.title | test("^\\[?WIP[\\]:]"; "i") | not) | "\(.number) \(.head.sha)"') + jq -r --arg branch "${PRIMARY_BRANCH}" '.[] | select(.base.ref == $branch) | select(.draft != true) | select(.title | test("^\\[?WIP[\\]:]"; "i") | not) | "\(.number) \(.head.sha)"') if [ -z "$PRS" ]; then - log "No open PRs targeting master" + log "No open PRs targeting ${PRIMARY_BRANCH}" exit 0 fi diff --git a/review/review-pr.sh b/review/review-pr.sh index 9511718..3cb9c76 100755 --- a/review/review-pr.sh +++ b/review/review-pr.sh @@ -9,8 +9,8 @@ # - Auto-creates follow-up issues for pre-existing bugs flagged by reviewer # - JSON output format with validation + retry # -# Peek while running: cat /tmp/harb-review-status -# Watch log: tail -f ~/scripts/harb-review/review.log +# Peek while running: cat /tmp/-review-status +# Watch log: tail -f /review/review.log set -euo pipefail @@ -21,12 +21,12 @@ source "$(dirname "$0")/../lib/env.sh" PR_NUMBER="${1:?Usage: review-pr.sh [--force]}" FORCE="${2:-}" REPO="${CODEBERG_REPO}" -REPO_ROOT="/home/debian/harb" +REPO_ROOT="${PROJECT_REPO_ROOT}" # Bot account for posting reviews (separate user required for branch protection approvals) API_BASE="${CODEBERG_API}" -LOCKFILE="/tmp/harb-review.lock" -STATUSFILE="/tmp/harb-review-status" +LOCKFILE="/tmp/${PROJECT_NAME}-review.lock" +STATUSFILE="/tmp/${PROJECT_NAME}-review-status" LOGDIR="${FACTORY_ROOT}/review" LOGFILE="$LOGDIR/review.log" MIN_MEM_MB=1500 @@ -91,8 +91,8 @@ log "${PR_TITLE} (${PR_HEAD}→${PR_BASE} ${PR_SHA:0:7})" if [ "$PR_STATE" != "open" ]; then log "SKIP: state=${PR_STATE}" cd "$REPO_ROOT" - git worktree remove "/tmp/harb-review-${PR_NUMBER}" --force 2>/dev/null || true - rm -rf "/tmp/harb-review-${PR_NUMBER}" 2>/dev/null || true + git worktree remove "/tmp/${PROJECT_NAME}-review-${PR_NUMBER}" --force 2>/dev/null || true + rm -rf "/tmp/${PROJECT_NAME}-review-${PR_NUMBER}" 2>/dev/null || true exit 0 fi @@ -181,7 +181,7 @@ fi status "checking out PR branch" cd "$REPO_ROOT" git fetch origin "$PR_HEAD" 2>/dev/null || true -REVIEW_WORKTREE="/tmp/harb-review-${PR_NUMBER}" +REVIEW_WORKTREE="/tmp/${PROJECT_NAME}-review-${PR_NUMBER}" if [ -d "$REVIEW_WORKTREE" ]; then cd "$REVIEW_WORKTREE" @@ -340,7 +340,7 @@ DEVRESP_EOF ${INCREMENTAL_DIFF} \`\`\` -### Full Diff (master..${PR_SHA:0:7}) +### Full Diff (${PRIMARY_BRANCH}..${PR_SHA:0:7}) \`\`\`diff ${DIFF} \`\`\`