init: seed framework reference content from agent-runtimes main repo
This commit is contained in:
220
harnesses/contexts/agent-repo/v1/finalize.sh
Normal file
220
harnesses/contexts/agent-repo/v1/finalize.sh
Normal file
@@ -0,0 +1,220 @@
|
||||
#!/bin/bash
|
||||
# Agent repo finalize script — auto-commit and push changes
|
||||
# AR-19, AR-20, AR-8, AR-32, F66
|
||||
set -euo pipefail
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# F66: Content-based secret scanning
|
||||
# Scans staged diff for credential patterns before committing.
|
||||
# Returns 0 if clean, 1 if secrets detected.
|
||||
# ---------------------------------------------------------------------------
|
||||
scan_for_secrets() {
|
||||
local diff found=0
|
||||
diff=$(cat)
|
||||
|
||||
# Extract only added lines (skip diff headers +++)
|
||||
local added
|
||||
added=$(printf '%s\n' "$diff" | grep '^+' | grep -v '^+++' || true)
|
||||
|
||||
if [[ -z "$added" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Pattern 1: AWS access key — AKIA followed by exactly 16 uppercase letters/digits
|
||||
if printf '%s\n' "$added" | grep -qE 'AKIA[0-9A-Z]{16}'; then
|
||||
echo "AWS access key pattern detected (AKIA...)"
|
||||
found=1
|
||||
fi
|
||||
|
||||
# Pattern 2: PEM private key header (RSA, EC, OPENSSH, etc.)
|
||||
if printf '%s\n' "$added" | grep -qE -- '-----BEGIN .* PRIVATE KEY-----'; then
|
||||
echo "PEM private key header detected"
|
||||
found=1
|
||||
fi
|
||||
|
||||
# Pattern 3: High-entropy credential assignment (40+ char value near key/token/secret keywords)
|
||||
if printf '%s\n' "$added" | grep -qiE \
|
||||
'(secret[._]?key|api[._]?key|access[._]?key|auth[._]?token|\bsecret\b|\btoken\b|\bpassword\b|\bpasswd\b)[[:space:]]*[=:][[:space:]]*['"'"'""]?[A-Za-z0-9+/=_-]{40,}'; then
|
||||
echo "High-entropy credential assignment detected"
|
||||
found=1
|
||||
fi
|
||||
|
||||
return $found
|
||||
}
|
||||
|
||||
# Allow sourcing for unit tests without running main
|
||||
if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then
|
||||
return 0 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "=== agent-repo/v1 finalize.sh ==="
|
||||
|
||||
WORKING_DIR="${AGENT_WORKING_DIR:-/workspace/project}"
|
||||
AGENT_OUTPUT_DIR="${AGENT_OUTPUT_DIR:-/workspace/.agent-output}"
|
||||
METADATA_FILE="$AGENT_OUTPUT_DIR/ci_metadata.json"
|
||||
AGENT_EXIT="${AGENT_EXIT_CODE:-0}"
|
||||
TASK_ID="${AGENT_TASK_ID:-unknown}"
|
||||
BRANCH="${AGENT_BRANCH:-}"
|
||||
REPO_URL="${AGENT_REPO_URL:-}"
|
||||
|
||||
mkdir -p "$AGENT_OUTPUT_DIR"
|
||||
|
||||
# AR-19: Mark working dir as safe (finalize may run as different user than agent)
|
||||
git config --global --add safe.directory "$WORKING_DIR"
|
||||
|
||||
# Change to working directory
|
||||
cd "$WORKING_DIR" || {
|
||||
echo "ERROR: Failed to change to working directory: $WORKING_DIR" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Verify this is a git repository (defensive: catches misconfigured payloads
|
||||
# where agent_repo field is missing but agent-repo harness is in the composite)
|
||||
if [ ! -d ".git" ]; then
|
||||
echo "ERROR: $WORKING_DIR is not a git repository." >&2
|
||||
echo "Hint: Ensure the task payload includes 'agent_repo: {repo_url: ..., branch: ...}'" >&2
|
||||
echo " so init.sh clones the repo before the agent runs." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# AR-32: Restore .gitignore from harness template to prevent removing entries
|
||||
HARNESS_GITIGNORE="/opt/harness/contexts/agent-repo/v1/.gitignore_template"
|
||||
if [ -f "$HARNESS_GITIGNORE" ]; then
|
||||
cat "$HARNESS_GITIGNORE" >> .gitignore
|
||||
fi
|
||||
|
||||
# AR-8: Check if there are any changes to commit
|
||||
git add -A
|
||||
|
||||
if [ -z "$(git status --porcelain)" ]; then
|
||||
echo "No changes to commit — skipping push (AR-8)"
|
||||
# Write metadata with agent_branch_pushed=false
|
||||
python3 -c "
|
||||
import json, os
|
||||
out = '$METADATA_FILE'
|
||||
meta = {}
|
||||
if os.path.isfile(out):
|
||||
try:
|
||||
with open(out) as f:
|
||||
meta = json.load(f)
|
||||
except Exception:
|
||||
meta = {}
|
||||
meta['agent_branch'] = '$BRANCH'
|
||||
meta['agent_repo_url'] = '$REPO_URL'
|
||||
meta['agent_branch_pushed'] = False
|
||||
with open(out, 'w') as f:
|
||||
json.dump(meta, f)
|
||||
print('Wrote ci_metadata.json (no-op: no changes)')
|
||||
"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# F66: Scan staged content for secrets before committing
|
||||
SCAN_FINDINGS=""
|
||||
SCAN_EXIT=0
|
||||
set +e
|
||||
SCAN_FINDINGS=$(git diff --cached | scan_for_secrets)
|
||||
SCAN_EXIT=$?
|
||||
set -e
|
||||
|
||||
if [ "$SCAN_EXIT" -ne 0 ]; then
|
||||
echo "SECRET SCAN BLOCKED COMMIT — credentials detected in staged content"
|
||||
echo "$SCAN_FINDINGS"
|
||||
# Write metadata indicating scan blocked the commit
|
||||
python3 -c "
|
||||
import json, os
|
||||
out = '$METADATA_FILE'
|
||||
meta = {}
|
||||
if os.path.isfile(out):
|
||||
try:
|
||||
with open(out) as f:
|
||||
meta = json.load(f)
|
||||
except Exception:
|
||||
meta = {}
|
||||
meta['agent_branch'] = '$BRANCH'
|
||||
meta['agent_repo_url'] = '$REPO_URL'
|
||||
meta['agent_branch_pushed'] = False
|
||||
meta['secret_scan_blocked'] = True
|
||||
meta['scan_findings'] = '''$SCAN_FINDINGS'''
|
||||
with open(out, 'w') as f:
|
||||
json.dump(meta, f)
|
||||
print('Wrote ci_metadata.json (blocked by secret scan)')
|
||||
"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# V3: Diff size check (warning only, does not block push)
|
||||
DIFF_LINES=$(git diff --cached --stat | tail -1 | grep -oP '\d+(?= insertion)' || echo "0")
|
||||
DIFF_KB=$(echo "$DIFF_LINES" | awk '{printf "%.1f", $1/1000}')
|
||||
MAX_DIFF_KB="${AGENT_MAX_DIFF_KB:-500}"
|
||||
if awk "BEGIN {exit !($DIFF_KB > $MAX_DIFF_KB)}"; then
|
||||
echo "WARNING: Large diff detected: ${DIFF_KB}KB (threshold: ${MAX_DIFF_KB}KB)"
|
||||
fi
|
||||
|
||||
# AR-6, AR-18: Build commit message based on agent outcome
|
||||
if [ "$AGENT_EXIT" = "0" ]; then
|
||||
OUTCOME="succeeded"
|
||||
else
|
||||
OUTCOME="failed (exit $AGENT_EXIT)"
|
||||
fi
|
||||
|
||||
# Get first line of prompt (safe, via tempfile)
|
||||
PROMPT_FIRST_LINE="${AGENT_PROMPT:-}"
|
||||
if [ -n "$PROMPT_FIRST_LINE" ]; then
|
||||
PROMPT_FIRST_LINE=$(echo "$PROMPT_FIRST_LINE" | head -1 | cut -c1-100)
|
||||
fi
|
||||
|
||||
# Write commit message to tempfile (AR-19: safe handling)
|
||||
COMMIT_MSG_FILE=$(mktemp)
|
||||
trap "rm -f $COMMIT_MSG_FILE" EXIT
|
||||
|
||||
if [ -n "$PROMPT_FIRST_LINE" ]; then
|
||||
echo "Agent task $TASK_ID ($OUTCOME): $PROMPT_FIRST_LINE" > "$COMMIT_MSG_FILE"
|
||||
else
|
||||
echo "Agent task $TASK_ID ($OUTCOME)" > "$COMMIT_MSG_FILE"
|
||||
fi
|
||||
|
||||
# Commit
|
||||
git commit -F "$COMMIT_MSG_FILE"
|
||||
|
||||
# Get commit SHA
|
||||
COMMIT_SHA=$(git rev-parse HEAD)
|
||||
|
||||
# AR-19: Always push — even if tests failed, partial work is better than lost work.
|
||||
# The task exit code and ci_metadata.json track whether tests passed.
|
||||
echo "Pushing branch $BRANCH to $REPO_URL..."
|
||||
if timeout 120 git push "$REPO_URL" "HEAD:refs/heads/$BRANCH" --force; then
|
||||
echo "Push succeeded"
|
||||
PUSHED=true
|
||||
else
|
||||
echo "ERROR: Push failed" >&2
|
||||
PUSHED=false
|
||||
fi
|
||||
|
||||
# Write ci_metadata.json
|
||||
python3 -c "
|
||||
import json, os
|
||||
out = '$METADATA_FILE'
|
||||
meta = {}
|
||||
if os.path.isfile(out):
|
||||
try:
|
||||
with open(out) as f:
|
||||
meta = json.load(f)
|
||||
except Exception:
|
||||
meta = {}
|
||||
meta['agent_branch'] = '$BRANCH'
|
||||
meta['agent_sha'] = '$COMMIT_SHA'
|
||||
meta['agent_repo_url'] = '$REPO_URL'
|
||||
meta['agent_branch_pushed'] = $( [ '$PUSHED' = 'true' ] && echo 'True' || echo 'False' )
|
||||
meta['diff_kb'] = float('$DIFF_KB') if '$DIFF_KB' else 0.0
|
||||
with open(out, 'w') as f:
|
||||
json.dump(meta, f)
|
||||
print('Wrote ci_metadata.json')
|
||||
"
|
||||
|
||||
if [ "$PUSHED" = "false" ]; then
|
||||
echo "ERROR: Push failed — branch $BRANCH was committed but not pushed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== agent-repo/v1 finalize.sh complete ==="
|
||||
Reference in New Issue
Block a user