#!/usr/bin/env bash # profile.sh — Helpers for agent .profile repo management # # Source after lib/env.sh and lib/formula-session.sh: # source "$(dirname "$0")/../lib/env.sh" # source "$(dirname "$0")/lib/formula-session.sh" # source "$(dirname "$0")/lib/profile.sh" # # Required globals: FORGE_TOKEN, FORGE_URL, AGENT_IDENTITY, PROFILE_REPO_PATH # # Functions: # profile_propose_formula NEW_FORMULA CONTENT REASON — create PR to update formula.toml set -euo pipefail # Internal log helper _profile_log() { if declare -f log >/dev/null 2>&1; then log "profile: $*" else printf '[%s] profile: %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "$*" >&2 fi } # ----------------------------------------------------------------------------- # profile_propose_formula — Propose a formula change via PR # # Creates a branch, writes updated formula.toml, opens a PR, and returns PR number. # Branch is protected (requires admin approval per #87). # # Args: # $1 - NEW_FORMULA_CONTENT: The complete new formula.toml content # $2 - REASON: Human-readable explanation of what changed and why # # Returns: # 0 on success, prints PR number to stdout # 1 on failure # # Example: # source "$(dirname "$0")/../lib/env.sh" # source "$(dirname "$0")/lib/formula-session.sh" # source "$(dirname "$0")/lib/profile.sh" # AGENT_IDENTITY="dev-bot" # ensure_profile_repo "$AGENT_IDENTITY" # profile_propose_formula "$new_formula" "Added new prompt pattern for code review" # ----------------------------------------------------------------------------- profile_propose_formula() { local new_formula="$1" local reason="$2" if [ -z "${AGENT_IDENTITY:-}" ]; then _profile_log "ERROR: AGENT_IDENTITY not set" return 1 fi if [ -z "${PROFILE_REPO_PATH:-}" ]; then _profile_log "ERROR: PROFILE_REPO_PATH not set — ensure_profile_repo not called" return 1 fi if [ -z "${FORGE_TOKEN:-}" ]; then _profile_log "ERROR: FORGE_TOKEN not set" return 1 fi if [ -z "${FORGE_URL:-}" ]; then _profile_log "ERROR: FORGE_URL not set" return 1 fi # Generate short description from reason for branch name local short_desc short_desc=$(printf '%s' "$reason" | \ tr '[:upper:]' '[:lower:]' | \ sed 's/[^a-z0-9 ]//g' | \ sed 's/ */ /g' | \ sed 's/^ *//;s/ *$//' | \ cut -c1-40 | \ tr ' ' '-') if [ -z "$short_desc" ]; then short_desc="formula-update" fi local branch_name="formula/${short_desc}" local formula_path="${PROFILE_REPO_PATH}/formula.toml" _profile_log "Proposing formula change: ${branch_name}" _profile_log "Reason: ${reason}" # Ensure we're on main branch and up-to-date _profile_log "Fetching .profile repo" ( cd "$PROFILE_REPO_PATH" || return 1 git fetch origin main --quiet 2>/dev/null || \ git fetch origin master --quiet 2>/dev/null || true # Reset to main/master if git checkout main --quiet 2>/dev/null; then git pull --ff-only origin main --quiet 2>/dev/null || true elif git checkout master --quiet 2>/dev/null; then git pull --ff-only origin master --quiet 2>/dev/null || true else _profile_log "ERROR: Failed to checkout main/master branch" return 1 fi # Create and checkout new branch git checkout -b "$branch_name" 2>/dev/null || { _profile_log "Branch ${branch_name} may already exist" git checkout "$branch_name" 2>/dev/null || return 1 } # Write formula.toml printf '%s' "$new_formula" > "$formula_path" # Commit the change git config user.name "${AGENT_IDENTITY}" || true git config user.email "${AGENT_IDENTITY}@users.noreply.codeberg.org" || true git add "$formula_path" git commit -m "formula: ${reason}" --no-verify || { _profile_log "No changes to commit (formula unchanged)" # Check if branch has any commits if git rev-parse HEAD >/dev/null 2>&1; then : # branch has commits, continue else _profile_log "ERROR: Failed to create commit" return 1 fi } # Push branch local remote="${FORGE_REMOTE:-origin}" git push --set-upstream "$remote" "$branch_name" --quiet 2>/dev/null || { _profile_log "ERROR: Failed to push branch" return 1 } _profile_log "Branch pushed: ${branch_name}" # Create PR local forge_url="${FORGE_URL%/}" local api_url="${forge_url}/api/v1/repos/${AGENT_IDENTITY}/.profile" local primary_branch="main" # Check if main or master is the primary branch if ! curl -sf -o /dev/null -w "%{http_code}" \ -H "Authorization: token ${FORGE_TOKEN}" \ "${api_url}/git/branches/main" 2>/dev/null | grep -q "200"; then primary_branch="master" fi local pr_title="formula: ${reason}" local pr_body="# Formula Update **Reason:** ${reason} --- *This PR was auto-generated by ${AGENT_IDENTITY}.* " local pr_response http_code local pr_json pr_json=$(jq -n \ --arg t "$pr_title" \ --arg b "$pr_body" \ --arg h "$branch_name" \ --arg base "$primary_branch" \ '{title:$t, body:$b, head:$h, base:$base}') || { _profile_log "ERROR: Failed to build PR JSON" return 1 } pr_response=$(curl -s -w "\n%{http_code}" -X POST \ -H "Authorization: token ${FORGE_TOKEN}" \ -H "Content-Type: application/json" \ "${api_url}/pulls" \ -d "$pr_json" || true) http_code=$(printf '%s\n' "$pr_response" | tail -1) pr_response=$(printf '%s\n' "$pr_response" | sed '$d') if [ "$http_code" = "201" ] || [ "$http_code" = "200" ]; then local pr_num pr_num=$(printf '%s' "$pr_response" | jq -r '.number') _profile_log "PR created: #${pr_num}" printf '%s' "$pr_num" return 0 else # Check if PR already exists (409 conflict) if [ "$http_code" = "409" ]; then local existing_pr existing_pr=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \ "${api_url}/pulls?state=open&head=${AGENT_IDENTITY}:formula/${short_desc}" 2>/dev/null | \ jq -r '.[0].number // empty') || true if [ -n "$existing_pr" ]; then _profile_log "PR already exists: #${existing_pr}" printf '%s' "$existing_pr" return 0 fi fi _profile_log "ERROR: Failed to create PR (HTTP ${http_code})" return 1 fi ) return $? }