refactor: make all scripts multi-project via env vars
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) <noreply@anthropic.com>
This commit is contained in:
parent
f16df6c53e
commit
90ef03a304
16 changed files with 117 additions and 116 deletions
23
.env.example
23
.env.example
|
|
@ -2,17 +2,23 @@
|
||||||
# Copy to .env and fill in your values.
|
# Copy to .env and fill in your values.
|
||||||
# NEVER commit .env to the repo.
|
# 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 API token (read from ~/.netrc by default, override here if needed)
|
||||||
# CODEBERG_TOKEN=
|
# CODEBERG_TOKEN=
|
||||||
|
|
||||||
# Codeberg review bot token (separate account for formal reviews)
|
# Codeberg review bot token (separate account for formal reviews)
|
||||||
REVIEW_BOT_TOKEN=
|
REVIEW_BOT_TOKEN=
|
||||||
|
|
||||||
# Woodpecker CI API token
|
# ── Woodpecker CI ─────────────────────────────────────────────────────────
|
||||||
WOODPECKER_TOKEN=
|
WOODPECKER_TOKEN=
|
||||||
|
|
||||||
# Woodpecker CI server URL
|
|
||||||
WOODPECKER_SERVER=http://localhost:8000
|
WOODPECKER_SERVER=http://localhost:8000
|
||||||
|
WOODPECKER_REPO_ID=2 # numeric repo ID in Woodpecker DB
|
||||||
|
|
||||||
# Woodpecker Postgres (for direct DB queries)
|
# Woodpecker Postgres (for direct DB queries)
|
||||||
WOODPECKER_DB_PASSWORD=
|
WOODPECKER_DB_PASSWORD=
|
||||||
|
|
@ -20,12 +26,5 @@ WOODPECKER_DB_USER=woodpecker
|
||||||
WOODPECKER_DB_HOST=127.0.0.1
|
WOODPECKER_DB_HOST=127.0.0.1
|
||||||
WOODPECKER_DB_NAME=woodpecker
|
WOODPECKER_DB_NAME=woodpecker
|
||||||
|
|
||||||
# Target Codeberg repo
|
# ── Tuning ────────────────────────────────────────────────────────────────
|
||||||
CODEBERG_REPO=johba/harb
|
CLAUDE_TIMEOUT=7200 # max seconds per Claude invocation
|
||||||
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
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
# {"status": "already_done", "reason": "..."}
|
# {"status": "already_done", "reason": "..."}
|
||||||
#
|
#
|
||||||
# Peek: cat /tmp/dev-agent-status
|
# Peek: cat /tmp/dev-agent-status
|
||||||
# Log: tail -f ~/scripts/harb-dev/dev-agent.log
|
# Log: tail -f dev-agent.log
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
@ -31,7 +31,7 @@ source "$(dirname "$0")/../lib/env.sh"
|
||||||
# --- Config ---
|
# --- Config ---
|
||||||
ISSUE="${1:?Usage: dev-agent.sh <issue-number>}"
|
ISSUE="${1:?Usage: dev-agent.sh <issue-number>}"
|
||||||
REPO="${CODEBERG_REPO}"
|
REPO="${CODEBERG_REPO}"
|
||||||
REPO_ROOT="${HARB_REPO_ROOT}"
|
REPO_ROOT="${PROJECT_REPO_ROOT}"
|
||||||
|
|
||||||
API="${CODEBERG_API}"
|
API="${CODEBERG_API}"
|
||||||
LOCKFILE="/tmp/dev-agent.lock"
|
LOCKFILE="/tmp/dev-agent.lock"
|
||||||
|
|
@ -39,7 +39,7 @@ STATUSFILE="/tmp/dev-agent-status"
|
||||||
LOGFILE="${FACTORY_ROOT}/dev/dev-agent.log"
|
LOGFILE="${FACTORY_ROOT}/dev/dev-agent.log"
|
||||||
PREFLIGHT_RESULT="/tmp/dev-agent-preflight.json"
|
PREFLIGHT_RESULT="/tmp/dev-agent-preflight.json"
|
||||||
BRANCH="fix/issue-${ISSUE}"
|
BRANCH="fix/issue-${ISSUE}"
|
||||||
WORKTREE="/tmp/harb-worktree-${ISSUE}"
|
WORKTREE="/tmp/${PROJECT_NAME}-worktree-${ISSUE}"
|
||||||
REVIEW_POLL_INTERVAL=300 # 5 min between review checks
|
REVIEW_POLL_INTERVAL=300 # 5 min between review checks
|
||||||
MAX_REVIEW_ROUNDS=5
|
MAX_REVIEW_ROUNDS=5
|
||||||
CLAUDE_TIMEOUT=7200
|
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')
|
description=$(echo "$ISSUE_TITLE" | sed 's/^feat:\s*//i;s/^fix:\s*//i;s/^refactor:\s*//i')
|
||||||
local line="- [${today}] ${description} (#${ISSUE})"
|
local line="- [${today}] ${description} (#${ISSUE})"
|
||||||
if [ ! -f "$state_file" ]; then
|
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
|
fi
|
||||||
echo "$line" >> "$state_file"
|
echo "$line" >> "$state_file"
|
||||||
log "STATE.md: ${line}"
|
log "STATE.md: ${line}"
|
||||||
|
|
@ -425,7 +425,7 @@ if [ -n "$EXISTING_PR" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
REVIEW_PROMPT="You are working in a git worktree at ${WORKTREE} on branch ${BRANCH}.
|
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}
|
## Issue: ${ISSUE_TITLE}
|
||||||
|
|
||||||
|
|
@ -539,28 +539,28 @@ else
|
||||||
status "creating worktree"
|
status "creating worktree"
|
||||||
cd "$REPO_ROOT"
|
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
|
if [ -d "$REPO_ROOT/.git/rebase-merge" ] || [ -d "$REPO_ROOT/.git/rebase-apply" ]; then
|
||||||
log "WARNING: stale rebase detected in main repo — aborting"
|
log "WARNING: stale rebase detected in main repo — aborting"
|
||||||
git rebase --abort 2>/dev/null || true
|
git rebase --abort 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
||||||
if [ "$CURRENT_BRANCH" != "master" ]; then
|
if [ "$CURRENT_BRANCH" != "${PRIMARY_BRANCH}" ]; then
|
||||||
log "WARNING: main repo on '$CURRENT_BRANCH' instead of master — switching"
|
log "WARNING: main repo on '$CURRENT_BRANCH' instead of ${PRIMARY_BRANCH} — switching"
|
||||||
git checkout master 2>/dev/null || true
|
git checkout "${PRIMARY_BRANCH}" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git fetch origin master 2>/dev/null
|
git fetch origin "${PRIMARY_BRANCH}" 2>/dev/null
|
||||||
git pull --ff-only origin master 2>/dev/null || true
|
git pull --ff-only origin "${PRIMARY_BRANCH}" 2>/dev/null || true
|
||||||
cleanup_worktree
|
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"
|
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
|
cleanup_labels
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
cd "$WORKTREE"
|
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
|
git submodule update --init --recursive 2>/dev/null || true
|
||||||
|
|
||||||
# Write STATE.md entry — included in the first commit, reads as done once PR merges
|
# 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)")
|
jq -r '.[] | "#\(.number) \(.title)"' 2>/dev/null || echo "(could not fetch)")
|
||||||
|
|
||||||
PROMPT="You are working in a git worktree at ${WORKTREE} on branch ${BRANCH}.
|
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}
|
## 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)
|
# But only treat as refusal if there are NO commits (claude might output JSON-like text AND commit)
|
||||||
cd "$WORKTREE"
|
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)
|
HAS_CHANGES=$(git status --porcelain)
|
||||||
|
|
||||||
if [ -n "$REFUSAL_JSON" ] && [ "$AHEAD" -eq 0 ] && [ -z "$HAS_CHANGES" ]; then
|
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})" \
|
--arg title "fix: ${ISSUE_TITLE} (#${ISSUE})" \
|
||||||
--rawfile body "/tmp/pr-body-${ISSUE}.txt" \
|
--rawfile body "/tmp/pr-body-${ISSUE}.txt" \
|
||||||
--arg head "$BRANCH" \
|
--arg head "$BRANCH" \
|
||||||
--arg base "master" \
|
--arg base "${PRIMARY_BRANCH}" \
|
||||||
'{title: $title, body: $body, head: $head, base: $base}' > /tmp/pr-request-${ISSUE}.json
|
'{title: $title, body: $body, head: $head, base: $base}' > /tmp/pr-request-${ISSUE}.json
|
||||||
|
|
||||||
PR_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
|
PR_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
|
||||||
|
|
@ -894,7 +894,7 @@ do_merge() {
|
||||||
if [ "$mergeable" = "false" ]; then
|
if [ "$mergeable" = "false" ]; then
|
||||||
log "PR #${PR_NUMBER} has merge conflicts — attempting rebase"
|
log "PR #${PR_NUMBER} has merge conflicts — attempting rebase"
|
||||||
local work_dir="${WORKTREE:-$REPO_ROOT}"
|
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"
|
log "rebase succeeded — force pushing"
|
||||||
(cd "$work_dir" && git push origin "${BRANCH}" --force-with-lease 2>&1) || true
|
(cd "$work_dir" && git push origin "${BRANCH}" --force-with-lease 2>&1) || true
|
||||||
# Wait for CI on the new commit
|
# Wait for CI on the new commit
|
||||||
|
|
@ -925,8 +925,8 @@ do_merge() {
|
||||||
if [ "$http_code" = "200" ] || [ "$http_code" = "204" ]; then
|
if [ "$http_code" = "200" ] || [ "$http_code" = "204" ]; then
|
||||||
log "PR #${PR_NUMBER} merged!"
|
log "PR #${PR_NUMBER} merged!"
|
||||||
|
|
||||||
# Update STATE.md on master (pull merged changes first)
|
# Update STATE.md on primary branch (pull merged changes first)
|
||||||
(cd "$REPO_ROOT" && git checkout master 2>/dev/null && git pull --ff-only origin master 2>/dev/null) || true
|
(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)"
|
append_state_log || log "WARNING: STATE.md update failed (non-fatal)"
|
||||||
|
|
||||||
curl -sf -X DELETE \
|
curl -sf -X DELETE \
|
||||||
|
|
@ -995,7 +995,7 @@ while [ "$REVIEW_ROUND" -lt "$MAX_REVIEW_ROUNDS" ]; do
|
||||||
if [ -n "$PIPELINE_NUM" ]; then
|
if [ -n "$PIPELINE_NUM" ]; then
|
||||||
FAILED_INFO=$(curl -sf \
|
FAILED_INFO=$(curl -sf \
|
||||||
-H "Authorization: Bearer ${WOODPECKER_TOKEN}" \
|
-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)
|
jq -r '.workflows[]?.children[]? | select(.state=="failure") | "\(.name)|\(.exit_code)"' | head -1)
|
||||||
FAILED_STEP=$(echo "$FAILED_INFO" | cut -d'|' -f1)
|
FAILED_STEP=$(echo "$FAILED_INFO" | cut -d'|' -f1)
|
||||||
FAILED_EXIT=$(echo "$FAILED_INFO" | cut -d'|' -f2)
|
FAILED_EXIT=$(echo "$FAILED_INFO" | cut -d'|' -f2)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# dev-poll.sh — Pull-based factory: find the next ready issue and start dev-agent
|
# 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
|
# 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.
|
# No "todo" label needed — readiness is derived from reality.
|
||||||
#
|
#
|
||||||
# Priority:
|
# Priority:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Factory Supervisor
|
# 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.
|
`factory-poll.sh` detected an issue it couldn't auto-fix.
|
||||||
|
|
||||||
## Priority Order
|
## Priority Order
|
||||||
|
|
@ -34,9 +34,11 @@ source ${FACTORY_ROOT}/lib/env.sh
|
||||||
This gives you:
|
This gives you:
|
||||||
- `codeberg_api GET "/pulls?state=open"` — Codeberg API (uses $CODEBERG_TOKEN)
|
- `codeberg_api GET "/pulls?state=open"` — Codeberg API (uses $CODEBERG_TOKEN)
|
||||||
- `wpdb -c "SELECT ..."` — Woodpecker Postgres (uses $WOODPECKER_DB_PASSWORD)
|
- `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
|
- `$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
|
- `$FACTORY_ROOT` — path to the dark-factory repo
|
||||||
|
|
||||||
## Escalation
|
## Escalation
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,15 @@
|
||||||
- Woodpecker CI at localhost:8000 (Docker backend)
|
- Woodpecker CI at localhost:8000 (Docker backend)
|
||||||
- Postgres DB: use `wpdb` helper from env.sh
|
- Postgres DB: use `wpdb` helper from env.sh
|
||||||
- Woodpecker API: use `woodpecker_api` 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
|
## Safe Fixes
|
||||||
- Retrigger CI: push empty commit to PR branch
|
- Retrigger CI: push empty commit to PR branch
|
||||||
```bash
|
```bash
|
||||||
cd /tmp/harb-worktree-<issue> && git commit --allow-empty -m "ci: retrigger" --no-verify && git push origin <branch> --force
|
cd /tmp/${PROJECT_NAME}-worktree-<issue> && git commit --allow-empty -m "ci: retrigger" --no-verify && git push origin <branch> --force
|
||||||
```
|
```
|
||||||
- Restart woodpecker-agent: `sudo systemctl restart woodpecker-agent`
|
- 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 <pipeline-number>`
|
- View failed steps: `bash ${FACTORY_ROOT}/lib/ci-debug.sh failures <pipeline-number>`
|
||||||
- View step logs: `bash ${FACTORY_ROOT}/lib/ci-debug.sh logs <pipeline-number> <step-name>`
|
- View step logs: `bash ${FACTORY_ROOT}/lib/ci-debug.sh logs <pipeline-number> <step-name>`
|
||||||
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
## Known Issues
|
## Known Issues
|
||||||
- Codeberg rate-limits SSH clones. `git` step fails with exit 128. Retrigger usually works.
|
- 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.
|
- `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`.
|
- CI images take hours to rebuild. Never run `docker system prune -a`.
|
||||||
|
|
||||||
## Lessons Learned
|
## Lessons Learned
|
||||||
|
|
@ -31,10 +31,10 @@
|
||||||
- Exit code 137 = OOM kill. Check memory, kill stale processes, retrigger.
|
- 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.
|
- `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.
|
When DeployLocal.sol changes the feeDest address, bootstrap-common.sh must also be updated.
|
||||||
Current feeDest = keccak256('harb.local.feeDest') = 0x8A9145E1Ea4C4d7FB08cF1011c8ac1F0e10F9383.
|
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.
|
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.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ Codeberg rate-limits SSH and HTTPS clones. Symptoms:
|
||||||
- **Do NOT retrigger** during a rate-limit storm. Wait 10-15 minutes.
|
- **Do NOT retrigger** during a rate-limit storm. Wait 10-15 minutes.
|
||||||
- Check if multiple pipelines failed on `git` step recently:
|
- Check if multiple pipelines failed on `git` step recently:
|
||||||
```bash
|
```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 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=<N> AND p.repo_id=2 AND s.state='failure';"
|
wpdb -c "SELECT s.name, s.exit_code FROM steps s JOIN pipelines p ON s.pipeline_id=p.id WHERE p.number=<N> 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.
|
- 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.
|
- Only retrigger after 15+ minutes of no CI activity.
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@
|
||||||
- `dev-agent.sh` uses `claude -p` for implementation, runs in git worktree
|
- `dev-agent.sh` uses `claude -p` for implementation, runs in git worktree
|
||||||
- Lock file: `/tmp/dev-agent.lock` (contains PID)
|
- Lock file: `/tmp/dev-agent.lock` (contains PID)
|
||||||
- Status file: `/tmp/dev-agent-status`
|
- Status file: `/tmp/dev-agent-status`
|
||||||
- Worktrees: `/tmp/harb-worktree-<issue-number>/`
|
- Worktrees: `/tmp/${PROJECT_NAME}-worktree-<issue-number>/`
|
||||||
|
|
||||||
## Safe Fixes
|
## Safe Fixes
|
||||||
- Remove stale lock: `rm -f /tmp/dev-agent.lock` (only if PID is dead)
|
- Remove stale lock: `rm -f /tmp/dev-agent.lock` (only if PID is dead)
|
||||||
- Kill stuck agent: `kill <pid>` then clean lock
|
- Kill stuck agent: `kill <pid>` then clean lock
|
||||||
- Restart on derailed PR: `bash ${FACTORY_ROOT}/dev/dev-agent.sh <issue-number> &`
|
- Restart on derailed PR: `bash ${FACTORY_ROOT}/dev/dev-agent.sh <issue-number> &`
|
||||||
- Clean worktree: `cd /home/debian/harb && git worktree remove /tmp/harb-worktree-<N> --force`
|
- Clean worktree: `cd $PROJECT_REPO_ROOT && git worktree remove /tmp/${PROJECT_NAME}-worktree-<N> --force`
|
||||||
- Remove `in-progress` label if agent died without cleanup:
|
- Remove `in-progress` label if agent died without cleanup:
|
||||||
```bash
|
```bash
|
||||||
codeberg_api DELETE "/issues/<N>/labels/in-progress"
|
codeberg_api DELETE "/issues/<N>/labels/in-progress"
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
|
|
||||||
## Dependency Resolution
|
## 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:
|
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
|
- Codeberg shares issue/PR numbering — no guaranteed relationship
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,14 @@
|
||||||
## Safe Fixes
|
## Safe Fixes
|
||||||
- Docker cleanup: `sudo docker system prune -f` (keeps images, removes stopped containers + dangling layers)
|
- Docker cleanup: `sudo docker system prune -f` (keeps images, removes stopped containers + dangling layers)
|
||||||
- Truncate factory logs >5MB: `truncate -s 0 <file>`
|
- Truncate factory logs >5MB: `truncate -s 0 <file>`
|
||||||
- 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;`
|
- 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/`
|
- Node module caches in worktrees: `rm -rf /tmp/${PROJECT_NAME}-worktree-*/node_modules/`
|
||||||
- Git garbage collection: `cd /home/debian/harb && git gc --prune=now`
|
- Git garbage collection: `cd $PROJECT_REPO_ROOT && git gc --prune=now`
|
||||||
|
|
||||||
## Dangerous (escalate)
|
## Dangerous (escalate)
|
||||||
- `docker system prune -a --volumes` — deletes ALL images including CI build cache
|
- `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
|
- Truncating Woodpecker DB tables other than log_entries
|
||||||
|
|
||||||
## Known Disk Hogs
|
## Known Disk Hogs
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,39 @@
|
||||||
# Git Best Practices
|
# Git Best Practices
|
||||||
|
|
||||||
## Environment
|
## Environment
|
||||||
- Repo: `/home/debian/harb`, remote: `codeberg.org/johba/harb`
|
- Repo: `$PROJECT_REPO_ROOT`, remote: `$PROJECT_REMOTE`
|
||||||
- Branch: `master` (protected — no direct push, PRs only)
|
- Branch: `$PRIMARY_BRANCH` (protected — no direct push, PRs only)
|
||||||
- Worktrees: `/tmp/harb-worktree-<issue>/`
|
- Worktrees: `/tmp/${PROJECT_NAME}-worktree-<issue>/`
|
||||||
|
|
||||||
## Safe Fixes
|
## Safe Fixes
|
||||||
- Abort stale rebase: `cd /home/debian/harb && git rebase --abort`
|
- Abort stale rebase: `cd $PROJECT_REPO_ROOT && git rebase --abort`
|
||||||
- Switch to master: `git checkout master`
|
- Switch to $PRIMARY_BRANCH: `git checkout $PRIMARY_BRANCH`
|
||||||
- Prune worktrees: `git worktree prune`
|
- Prune worktrees: `git worktree prune`
|
||||||
- Reset dirty state: `git checkout -- .` (only uncommitted changes)
|
- 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
|
## 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
|
```bash
|
||||||
cd /tmp/harb-worktree-<issue> || git worktree add /tmp/harb-worktree-<issue> <branch>
|
cd /tmp/${PROJECT_NAME}-worktree-<issue> || git worktree add /tmp/${PROJECT_NAME}-worktree-<issue> <branch>
|
||||||
cd /tmp/harb-worktree-<issue>
|
cd /tmp/${PROJECT_NAME}-worktree-<issue>
|
||||||
git fetch origin master
|
git fetch origin $PRIMARY_BRANCH
|
||||||
git rebase origin/master
|
git rebase origin/$PRIMARY_BRANCH
|
||||||
# If conflict is trivial (NatSpec, comments): resolve and continue
|
# If conflict is trivial (NatSpec, comments): resolve and continue
|
||||||
# If conflict is code logic: escalate to Clawy
|
# If conflict is code logic: escalate to Clawy
|
||||||
git push origin <branch> --force
|
git push origin <branch> --force
|
||||||
```
|
```
|
||||||
- **Stale rebase**: `git rebase --abort && git checkout master`
|
- **Stale rebase**: `git rebase --abort && git checkout $PRIMARY_BRANCH`
|
||||||
- **Wrong branch**: `git checkout master`
|
- **Wrong branch**: `git checkout $PRIMARY_BRANCH`
|
||||||
|
|
||||||
## Dangerous (escalate)
|
## Dangerous (escalate)
|
||||||
- `git reset --hard` on any branch with unpushed work
|
- `git reset --hard` on any branch with unpushed work
|
||||||
- Deleting remote branches
|
- Deleting remote branches
|
||||||
- Force-pushing to any branch
|
- Force-pushing to any branch
|
||||||
- Anything on the master branch directly
|
- Anything on the $PRIMARY_BRANCH branch directly
|
||||||
|
|
||||||
## Known Issues
|
## 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.
|
- Stale rebases (detached HEAD) break all worktree creation — silent factory stall.
|
||||||
- `git worktree add` fails if target directory exists (even empty). Remove first.
|
- `git worktree add` fails if target directory exists (even empty). Remove first.
|
||||||
- Many old branches exist locally (100+). Normal — don't bulk-delete.
|
- Many old branches exist locally (100+). Normal — don't bulk-delete.
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@
|
||||||
## Safe Fixes (no permission needed)
|
## Safe Fixes (no permission needed)
|
||||||
- Kill stale `claude` processes (>3h old): `pgrep -f "claude" --older 10800 | xargs kill`
|
- 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`
|
- 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
|
- Kill orphan node processes from dead worktrees
|
||||||
|
|
||||||
## Dangerous (escalate)
|
## Dangerous (escalate)
|
||||||
- `docker system prune -a --volumes` — kills CI images, hours to rebuild
|
- `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
|
- OOM that survives all safe fixes — needs human decision on what to kill
|
||||||
|
|
||||||
## Known Memory Hogs
|
## Known Memory Hogs
|
||||||
|
|
@ -26,4 +26,4 @@
|
||||||
## Lessons Learned
|
## Lessons Learned
|
||||||
- After killing processes, always `sync && echo 3 | sudo tee /proc/sys/vm/drop_caches`
|
- 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
|
- 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.
|
||||||
|
|
|
||||||
|
|
@ -75,9 +75,10 @@ if [ "${AVAIL_MB:-9999}" -lt 500 ] || { [ "${SWAP_USED_MB:-0}" -gt 3000 ] && [ "
|
||||||
fixed "Dropped filesystem caches"
|
fixed "Dropped filesystem caches"
|
||||||
|
|
||||||
# Restart Anvil if it's bloated (>1GB RSS)
|
# 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
|
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
|
fi
|
||||||
|
|
||||||
# Re-check after fixes
|
# Re-check after fixes
|
||||||
|
|
@ -116,12 +117,12 @@ if [ "${DISK_PERCENT:-0}" -gt 80 ]; then
|
||||||
done
|
done
|
||||||
|
|
||||||
# Clean old worktrees
|
# 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
|
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
|
for wt in $IDLE_WORKTREES; do
|
||||||
# Only remove if dev-agent is not running on it
|
# 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
|
if ! pgrep -f "dev-agent.sh ${ISSUE_NUM}" >/dev/null 2>&1; then
|
||||||
rm -rf "$wt" && fixed "Removed stale worktree: $wt"
|
rm -rf "$wt" && fixed "Removed stale worktree: $wt"
|
||||||
fi
|
fi
|
||||||
|
|
@ -153,10 +154,10 @@ fi
|
||||||
status "P2: checking factory"
|
status "P2: checking factory"
|
||||||
|
|
||||||
# CI stuck
|
# 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"
|
[ "${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"
|
[ "${PENDING_CI:-0}" -gt 0 ] && p2 "CI: ${PENDING_CI} pipeline(s) pending >30min"
|
||||||
|
|
||||||
# Dev-agent health
|
# Dev-agent health
|
||||||
|
|
@ -177,19 +178,19 @@ if [ -f "$DEV_LOCK" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Git repo health
|
# 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_BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
|
||||||
GIT_REBASE=$([ -d .git/rebase-merge ] || [ -d .git/rebase-apply ] && echo "yes" || echo "no")
|
GIT_REBASE=$([ -d .git/rebase-merge ] || [ -d .git/rebase-apply ] && echo "yes" || echo "no")
|
||||||
|
|
||||||
if [ "$GIT_REBASE" = "yes" ]; then
|
if [ "$GIT_REBASE" = "yes" ]; then
|
||||||
git rebase --abort 2>/dev/null && git checkout master 2>/dev/null && \
|
git rebase --abort 2>/dev/null && git checkout "${PRIMARY_BRANCH}" 2>/dev/null && \
|
||||||
fixed "Aborted stale rebase, switched to master" || \
|
fixed "Aborted stale rebase, switched to ${PRIMARY_BRANCH}" || \
|
||||||
p2 "Git: stale rebase, auto-abort failed"
|
p2 "Git: stale rebase, auto-abort failed"
|
||||||
fi
|
fi
|
||||||
if [ "$GIT_BRANCH" != "master" ] && [ "$GIT_BRANCH" != "unknown" ]; then
|
if [ "$GIT_BRANCH" != "${PRIMARY_BRANCH}" ] && [ "$GIT_BRANCH" != "unknown" ]; then
|
||||||
git checkout master 2>/dev/null && \
|
git checkout "${PRIMARY_BRANCH}" 2>/dev/null && \
|
||||||
fixed "Switched main repo from '${GIT_BRANCH}' to master" || \
|
fixed "Switched main repo from '${GIT_BRANCH}' to ${PRIMARY_BRANCH}" || \
|
||||||
p2 "Git: on '${GIT_BRANCH}' instead of master"
|
p2 "Git: on '${GIT_BRANCH}' instead of ${PRIMARY_BRANCH}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ log "Invoking claude -p for grooming"
|
||||||
# Build issue summary for context (titles + labels + deps)
|
# Build issue summary for context (titles + labels + deps)
|
||||||
ISSUE_SUMMARY=$(echo "$ISSUES_JSON" | jq -r '.[] | "#\(.number) [\(.labels | map(.name) | join(","))] \(.title)"')
|
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
|
## Current open issues
|
||||||
$ISSUE_SUMMARY
|
$ISSUE_SUMMARY
|
||||||
|
|
@ -161,18 +161,15 @@ $(echo -e "$PROBLEMS")
|
||||||
|
|
||||||
## Tools available
|
## Tools available
|
||||||
- Codeberg API: use curl with the CODEBERG_TOKEN env var (already set in your environment)
|
- 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
|
- Base URL: ${CODEBERG_API}
|
||||||
- Read issue: \`curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" 'https://codeberg.org/api/v1/repos/johba/harb/issues/{number}' | jq '.body'\`
|
- 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' 'https://codeberg.org/api/v1/repos/johba/harb/issues/{number}/labels' -d '{\"labels\":[652336]}'\` (652336=backlog, 1219499=tech-debt)
|
- 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' 'https://codeberg.org/api/v1/repos/johba/harb/issues/{number}/comments' -d '{\"body\":\"...\"}'\`
|
- 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' 'https://codeberg.org/api/v1/repos/johba/harb/issues/{number}' -d '{\"state\":\"closed\"}'\`
|
- 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' 'https://codeberg.org/api/v1/repos/johba/harb/issues/{number}' -d '{\"body\":\"new body\"}'\`
|
- 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
|
- 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:
|
- You're running in the project repo root. Read README.md and any docs/ files 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)
|
|
||||||
|
|
||||||
## Primary mission: promote tech-debt → backlog
|
## 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:
|
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.
|
- 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."
|
- 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" \
|
claude -p "$PROMPT" \
|
||||||
--model sonnet \
|
--model sonnet \
|
||||||
--dangerously-skip-permissions \
|
--dangerously-skip-permissions \
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,9 @@ set -euo pipefail
|
||||||
# Load shared environment
|
# Load shared environment
|
||||||
source "$(dirname "$0")/../lib/env.sh"
|
source "$(dirname "$0")/../lib/env.sh"
|
||||||
|
|
||||||
export WOODPECKER_SERVER="http://localhost:8000"
|
|
||||||
# WOODPECKER_TOKEN loaded from .env via env.sh
|
# WOODPECKER_TOKEN loaded from .env via env.sh
|
||||||
REPO="johba/harb"
|
REPO="${CODEBERG_REPO}"
|
||||||
API="${WOODPECKER_SERVER}/api/repos/2"
|
API="${WOODPECKER_SERVER}/api/repos/${WOODPECKER_REPO_ID}"
|
||||||
|
|
||||||
api() {
|
api() {
|
||||||
curl -sf -H "Authorization: Bearer ${WOODPECKER_TOKEN}" "${API}/$1"
|
curl -sf -H "Authorization: Bearer ${WOODPECKER_TOKEN}" "${API}/$1"
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,14 @@ if [ -z "${CODEBERG_TOKEN:-}" ]; then
|
||||||
fi
|
fi
|
||||||
export CODEBERG_TOKEN
|
export CODEBERG_TOKEN
|
||||||
|
|
||||||
# Defaults
|
# Project config
|
||||||
export CODEBERG_REPO="${CODEBERG_REPO:-johba/harb}"
|
export CODEBERG_REPO="${CODEBERG_REPO:-johba/harb}"
|
||||||
export CODEBERG_API="${CODEBERG_API:-https://codeberg.org/api/v1/repos/${CODEBERG_REPO}}"
|
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 WOODPECKER_SERVER="${WOODPECKER_SERVER:-http://localhost:8000}"
|
||||||
export CLAUDE_TIMEOUT="${CLAUDE_TIMEOUT:-7200}"
|
export CLAUDE_TIMEOUT="${CLAUDE_TIMEOUT:-7200}"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# review-poll.sh — Poll open PRs and review those with green CI
|
# review-poll.sh — Poll open PRs and review those with green CI
|
||||||
#
|
#
|
||||||
# Peek while running: cat /tmp/harb-review-status
|
# Peek while running: cat /tmp/<project>-review-status
|
||||||
# Full log: tail -f ~/scripts/harb-review/review.log
|
# Full log: tail -f <factory-root>/review/review.log
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Load shared environment
|
# Load shared environment
|
||||||
source "$(dirname "$0")/../lib/env.sh"
|
source "$(dirname "$0")/../lib/env.sh"
|
||||||
|
|
||||||
export HOME="${HOME:-/home/debian}"
|
|
||||||
|
|
||||||
REPO="${CODEBERG_REPO}"
|
REPO="${CODEBERG_REPO}"
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
@ -35,10 +34,10 @@ log "--- Poll start ---"
|
||||||
|
|
||||||
PRS=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
|
PRS=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||||
"${API_BASE}/pulls?state=open&limit=20" | \
|
"${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
|
if [ -z "$PRS" ]; then
|
||||||
log "No open PRs targeting master"
|
log "No open PRs targeting ${PRIMARY_BRANCH}"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@
|
||||||
# - Auto-creates follow-up issues for pre-existing bugs flagged by reviewer
|
# - Auto-creates follow-up issues for pre-existing bugs flagged by reviewer
|
||||||
# - JSON output format with validation + retry
|
# - JSON output format with validation + retry
|
||||||
#
|
#
|
||||||
# Peek while running: cat /tmp/harb-review-status
|
# Peek while running: cat /tmp/<project>-review-status
|
||||||
# Watch log: tail -f ~/scripts/harb-review/review.log
|
# Watch log: tail -f <factory-root>/review/review.log
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
@ -21,12 +21,12 @@ source "$(dirname "$0")/../lib/env.sh"
|
||||||
PR_NUMBER="${1:?Usage: review-pr.sh <pr-number> [--force]}"
|
PR_NUMBER="${1:?Usage: review-pr.sh <pr-number> [--force]}"
|
||||||
FORCE="${2:-}"
|
FORCE="${2:-}"
|
||||||
REPO="${CODEBERG_REPO}"
|
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)
|
# Bot account for posting reviews (separate user required for branch protection approvals)
|
||||||
API_BASE="${CODEBERG_API}"
|
API_BASE="${CODEBERG_API}"
|
||||||
LOCKFILE="/tmp/harb-review.lock"
|
LOCKFILE="/tmp/${PROJECT_NAME}-review.lock"
|
||||||
STATUSFILE="/tmp/harb-review-status"
|
STATUSFILE="/tmp/${PROJECT_NAME}-review-status"
|
||||||
LOGDIR="${FACTORY_ROOT}/review"
|
LOGDIR="${FACTORY_ROOT}/review"
|
||||||
LOGFILE="$LOGDIR/review.log"
|
LOGFILE="$LOGDIR/review.log"
|
||||||
MIN_MEM_MB=1500
|
MIN_MEM_MB=1500
|
||||||
|
|
@ -91,8 +91,8 @@ log "${PR_TITLE} (${PR_HEAD}→${PR_BASE} ${PR_SHA:0:7})"
|
||||||
if [ "$PR_STATE" != "open" ]; then
|
if [ "$PR_STATE" != "open" ]; then
|
||||||
log "SKIP: state=${PR_STATE}"
|
log "SKIP: state=${PR_STATE}"
|
||||||
cd "$REPO_ROOT"
|
cd "$REPO_ROOT"
|
||||||
git worktree remove "/tmp/harb-review-${PR_NUMBER}" --force 2>/dev/null || true
|
git worktree remove "/tmp/${PROJECT_NAME}-review-${PR_NUMBER}" --force 2>/dev/null || true
|
||||||
rm -rf "/tmp/harb-review-${PR_NUMBER}" 2>/dev/null || true
|
rm -rf "/tmp/${PROJECT_NAME}-review-${PR_NUMBER}" 2>/dev/null || true
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -181,7 +181,7 @@ fi
|
||||||
status "checking out PR branch"
|
status "checking out PR branch"
|
||||||
cd "$REPO_ROOT"
|
cd "$REPO_ROOT"
|
||||||
git fetch origin "$PR_HEAD" 2>/dev/null || true
|
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
|
if [ -d "$REVIEW_WORKTREE" ]; then
|
||||||
cd "$REVIEW_WORKTREE"
|
cd "$REVIEW_WORKTREE"
|
||||||
|
|
@ -340,7 +340,7 @@ DEVRESP_EOF
|
||||||
${INCREMENTAL_DIFF}
|
${INCREMENTAL_DIFF}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
### Full Diff (master..${PR_SHA:0:7})
|
### Full Diff (${PRIMARY_BRANCH}..${PR_SHA:0:7})
|
||||||
\`\`\`diff
|
\`\`\`diff
|
||||||
${DIFF}
|
${DIFF}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue