fix: feat(20e): formula evolution — agent proposes changes via PR to .profile (#88)
This commit is contained in:
parent
b17f15e071
commit
471d24fa23
2 changed files with 211 additions and 1 deletions
|
|
@ -27,7 +27,7 @@ disinto/ (code repo)
|
||||||
│ preflight.sh — pre-flight data collection for supervisor formula
|
│ preflight.sh — pre-flight data collection for supervisor formula
|
||||||
│ supervisor-poll.sh — legacy bash orchestrator (superseded)
|
│ supervisor-poll.sh — legacy bash orchestrator (superseded)
|
||||||
├── vault/ vault-env.sh — shared env setup (vault redesign in progress, see #73-#77)
|
├── vault/ vault-env.sh — shared env setup (vault redesign in progress, see #73-#77)
|
||||||
├── lib/ env.sh, agent-session.sh, ci-helpers.sh, ci-debug.sh, load-project.sh, parse-deps.sh, guard.sh, mirrors.sh, pr-lifecycle.sh, issue-lifecycle.sh, worktree.sh, build-graph.py
|
├── lib/ env.sh, agent-session.sh, ci-helpers.sh, ci-debug.sh, load-project.sh, parse-deps.sh, guard.sh, mirrors.sh, pr-lifecycle.sh, issue-lifecycle.sh, worktree.sh, formula-session.sh, profile.sh, build-graph.py
|
||||||
├── projects/ *.toml.example — templates; *.toml — local per-box config (gitignored)
|
├── projects/ *.toml.example — templates; *.toml — local per-box config (gitignored)
|
||||||
├── formulas/ Issue templates (TOML specs for multi-step agent tasks)
|
├── formulas/ Issue templates (TOML specs for multi-step agent tasks)
|
||||||
└── docs/ Protocol docs (PHASE-PROTOCOL.md, EVIDENCE-ARCHITECTURE.md)
|
└── docs/ Protocol docs (PHASE-PROTOCOL.md, EVIDENCE-ARCHITECTURE.md)
|
||||||
|
|
|
||||||
210
lib/profile.sh
Normal file
210
lib/profile.sh
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
#!/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 $?
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue