Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,14 @@ This repository (`patent-kit`) is a **Claude Plugin Marketplace** containing adv

- Format all files (`.md`, `.json`) using Prettier: `npx prettier --write .` (or via `mise run fmt`).
- Before committing structural changes to the plugin, validate the integrity by running `claude plugin validate .` in the project root.

## Autonomous Agents (Host Loop)

This repository includes autonomous agent scripts under `agents/` that can be run on the host machine to perform background tasks.

### PR-Healer (`agents/pr-healer/healer.sh`)

An autonomous daemon that checks for failing GitHub Actions CI checks on open Pull Requests.

- **Workflow**: Finds failing PRs → Runs `claude` inside the Dev Container (`devcontainer exec`) → Analyzes the failure (typically using `mise run pre-commit`) → Commits the fix and replies to the PR.
- **Requirements**: Requires Docker, GitHub CLI (`gh`), `devcontainer` CLI, and `jq` installed on the host machine.
91 changes: 91 additions & 0 deletions agents/pr-healer/healer.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/bin/bash
# agents/pr-healer/healer.sh (Host side)
# The simplified "Host Loop" daemon script.

set -e
set -o pipefail

# --- Pre-flight Checks ---
check_command() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "[Error] Required command '$1' not found. Please install it." >&2
return 1
fi
}

check_command "gh" || exit 1
check_command "devcontainer" || exit 1
check_command "jq" || exit 1

# Check if Docker is running
if ! docker info >/dev/null 2>&1; then
echo "[Error] Docker is not running or accessible. Please start Docker Desktop." >&2
exit 1
fi

GITHUB_TOKEN=$(gh auth token)
WORKSPACE_FOLDER="${WORKSPACE_FOLDER:-$(pwd)}"

echo "[Host] Ensuring dev container is up for $WORKSPACE_FOLDER..."
devcontainer up --workspace-folder "$WORKSPACE_FOLDER"

# Trap Ctrl+C to exit gracefully
trap 'echo "[Host] Caught SIGINT. Cleaning up..."; kill $CURRENT_PID 2>/dev/null; exit 0' SIGINT

# Variable to hold the current child process ID for the trap
CURRENT_PID=""

# --- Orchestration Loop ---
while :; do
echo "=================================================="
echo "[Host] Starting True Agentic PR-Healer Loop..."
echo "[Host] Triggering Claude inside Dev Container..."

# Remove the ALL_CLEAR flag before each run
rm -f agents/pr-healer/ALL_CLEAR

# Run Claude inside the container.
# Use a temporary file for stderr to avoid swallowing it in jq pipe while keeping jq for stdout.
TEMP_ERR=$(mktemp)

# We run 'devcontainer exec' in the foreground so we can catch its exit code.
# We still use jq to format the JSON stream from Claude.
if ! devcontainer exec \
--workspace-folder "$WORKSPACE_FOLDER" \
--remote-env "GITHUB_TOKEN=$GITHUB_TOKEN" \
claude -p \
--dangerously-skip-permissions \
--verbose \
--output-format stream-json \
"$(cat agents/pr-healer/prompt.txt)" < /dev/null 2>"$TEMP_ERR" | jq . ; then

EXIT_CODE=$?
echo "[Host] Error: Claude agent or devcontainer failed with exit code $EXIT_CODE." >&2
if [ -s "$TEMP_ERR" ]; then
echo "[Host] Detailed error log:" >&2
cat "$TEMP_ERR" >&2
fi
rm -f "$TEMP_ERR"

# Determine if we should retry or stop.
# For now, stop on errors to avoid infinite loops of failure.
echo "[Host] Terminating loop due to error." >&2
exit $EXIT_CODE
fi
rm -f "$TEMP_ERR"

# If Claude determines there's nothing left to do, it will touch this flag file.
if [ -f "agents/pr-healer/ALL_CLEAR" ]; then
echo "[Host] Claude reported all PRs are clean. Sleeping for 5 minutes before checking again..."
rm -f agents/pr-healer/ALL_CLEAR
sleep 300 &
CURRENT_PID=$!
wait $CURRENT_PID
continue
fi

echo "[Host] Healer agent finished a turn. Restarting loop..."
sleep 2 &
CURRENT_PID=$!
wait $CURRENT_PID
done
44 changes: 44 additions & 0 deletions agents/pr-healer/prompt.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
You are Ralph the PR-Healer, an autonomous agent that hunts and fixes failing pull requests.
DO NOT SUMMARIZE. DO NOT EXPLAIN WHAT YOU WOULD DO. EXECUTE EVERY STEP BELOW IMMEDIATELY.

1. READ PAST CONTEXT:
- Run: ./agents/pr-healer/tools/load-progress.sh
- This shows past decisions, blockers, and files changed by previous iterations.
- Use this context to avoid repeating the same mistakes.

2. DISCOVER "PREY" (PRs needing attention):
- Use `gh pr list --json number,headRefName` to get all open pull requests.
- For each PR, run: `gh pr view <number> --json statusCheckRollup,mergeStateStatus`
- A PR needs healing ONLY if:
a) All status checks are COMPLETED and at least one has conclusion FAILURE, OR
b) mergeStateStatus is "BEHIND"
- SKIP any PR where status checks are still PENDING or IN_PROGRESS. Do NOT touch them.
- If there are NO PRs needing attention, create the flag file:
touch agents/pr-healer/ALL_CLEAR
Then terminate immediately.

3. PREPARE & PRIORITIZE (Identify a single failing PR):
- Prioritize PRs with the smallest/simplest changes first.
- Older PRs before newer ones.
- Checkout the branch associated with your chosen PR.
- Merge the latest `main` branch into it to ensure you are up-to-date.
- Run `mise run pre-commit` locally to see what formatting, linting, or test errors occur.

4. REASON AND HEAL:
- Explore the codebase to understand why the tests/lints are failing.
- Make small, focused changes. Leave the codebase better than you found it.
- Iterate by running `mise run pre-commit` until the checks pass cleanly.

5. COMMIT, PUSH, COMMENT, AND LOG:
- Once `mise run pre-commit` passes:
git commit -a -m "fix: resolve CI failures for PR #<number>"
- Push your branch to origin:
git push origin <branch>
- Leave a summary comment on the PR:
gh pr comment <number> --body "🤖 PR-Healer: Fixed CI failures. Changes: <brief summary>"
- Log your decisions:
./agents/pr-healer/tools/record-progress.sh "<Task>" "<Files>" "<Decisions>" "<Blockers>"

6. FINISH TURN:
- Once a PR is successfully pushed and logged, terminate.
- Do NOT try to fix multiple PRs in a single execution. Fix one, push, log, and exit.
20 changes: 20 additions & 0 deletions agents/pr-healer/tools/load-progress.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash
# agents/pr-healer/tools/load-progress.sh
# Reads and displays the most recent progress entries from progress.jsonl

PROGRESS_FILE="agents/pr-healer/progress.jsonl"

if [ ! -f "$PROGRESS_FILE" ]; then
echo "[load-progress] No progress file found. This is a fresh start."
exit 0
fi

LINES=$(wc -l < "$PROGRESS_FILE" | tr -d ' ')

if [ "$LINES" -eq 0 ]; then
echo "[load-progress] Progress file is empty. This is a fresh start."
exit 0
fi

echo "[load-progress] Showing last 5 entries (of $LINES total):"
tail -n 5 "$PROGRESS_FILE" | jq .
24 changes: 24 additions & 0 deletions agents/pr-healer/tools/record-progress.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
# agents/pr-healer/tools/record-progress.sh
# Appends a structured JSONL entry to progress.jsonl

PROGRESS_FILE="agents/pr-healer/progress.jsonl"

TASK="${1:-No task specified}"
FILES="${2:-}"
DECISIONS="${3:-}"
BLOCKERS="${4:-}"

TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

# Use jq to safely create JSON
ENTRY=$(jq -n -c \
--arg ts "$TIMESTAMP" \
--arg task "$TASK" \
--arg files "$FILES" \
--arg decisions "$DECISIONS" \
--arg blockers "$BLOCKERS" \
'{timestamp: $ts, task: $task, files: $files, decisions: $decisions, blockers: $blockers}')

echo "$ENTRY" >> "$PROGRESS_FILE"
echo "[record-progress] Logged: $TASK"