From b3b630ee7b47455084825700cfce9ac49f6b400d Mon Sep 17 00:00:00 2001 From: William Easton Date: Wed, 11 Feb 2026 15:52:54 -0600 Subject: [PATCH] Updates to github actions / workflows for claude --- .github/actions/run-claude/action.yml | 95 ++++++ .../scripts/mention/gh-get-review-threads.sh | 62 ++++ .../mention/gh-resolve-review-thread.sh | 61 ++++ .github/scripts/pr-review/pr-comment.sh | 251 +++++++++++++++ .github/scripts/pr-review/pr-diff.sh | 128 ++++++++ .../scripts/pr-review/pr-existing-comments.sh | 190 +++++++++++ .../scripts/pr-review/pr-remove-comment.sh | 84 +++++ .github/scripts/pr-review/pr-review.sh | 143 +++++++++ .github/workflows/martian-issue-triage.yml | 178 ----------- .github/workflows/martian-triage-issue.yml | 204 ++++++++++++ .github/workflows/marvin-comment-on-issue.yml | 143 +++++++++ .github/workflows/marvin-comment-on-pr.yml | 294 ++++++++++++++++++ .github/workflows/marvin.yml | 87 ------ 13 files changed, 1655 insertions(+), 265 deletions(-) create mode 100644 .github/actions/run-claude/action.yml create mode 100755 .github/scripts/mention/gh-get-review-threads.sh create mode 100755 .github/scripts/mention/gh-resolve-review-thread.sh create mode 100755 .github/scripts/pr-review/pr-comment.sh create mode 100755 .github/scripts/pr-review/pr-diff.sh create mode 100755 .github/scripts/pr-review/pr-existing-comments.sh create mode 100755 .github/scripts/pr-review/pr-remove-comment.sh create mode 100755 .github/scripts/pr-review/pr-review.sh delete mode 100644 .github/workflows/martian-issue-triage.yml create mode 100644 .github/workflows/martian-triage-issue.yml create mode 100644 .github/workflows/marvin-comment-on-issue.yml create mode 100644 .github/workflows/marvin-comment-on-pr.yml delete mode 100644 .github/workflows/marvin.yml diff --git a/.github/actions/run-claude/action.yml b/.github/actions/run-claude/action.yml new file mode 100644 index 000000000..fff6788a6 --- /dev/null +++ b/.github/actions/run-claude/action.yml @@ -0,0 +1,95 @@ +# Composite Action for running Claude Code Action +# +# Wraps anthropics/claude-code-action with MCP server configuration. +# Template based on elastic/ai-github-actions base action. +# +# Usage: +# - uses: ./.github/actions/run-claude +# with: +# prompt: "Your prompt here" +# claude-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} +# github-token: ${{ steps.marvin-token.outputs.token }} +# allowed-tools: "Edit,Read,Write,Bash(*),mcp__github__add_issue_comment" +# +name: "Run Claude" +description: "Run Claude Code with MCP servers" +author: "FastMCP" + +branding: + icon: "cpu" + color: "orange" + +inputs: + prompt: + description: "Prompt to pass to Claude" + required: true + + claude-oauth-token: + description: "Claude Code OAuth token for authentication" + required: true + + github-token: + description: "GitHub token for Claude to operate with" + required: true + + allowed-tools: + description: "Comma-separated list of allowed tools (e.g. Edit,Write,Bash(npm test))" + required: false + default: "" + + model: + description: "Model to use for Claude" + required: false + default: "claude-opus-4-6" + + allowed-bots: + description: "Allowed bot usernames, or '*' for all bots" + required: false + default: "" + + track-progress: + description: "Whether Claude should track progress" + required: false + default: "true" + + mcp-servers: + description: "MCP server configuration JSON" + required: false + default: '{"mcpServers":{"agents-md-generator":{"type":"http","url":"https://agents-md-generator.fastmcp.app/mcp"},"public-code-search":{"type":"http","url":"https://public-code-search.fastmcp.app/mcp"}}}' + + trigger-phrase: + description: "Trigger phrase (for mention workflows)" + required: false + default: "/marvin" + +outputs: + conclusion: + description: "The conclusion of the Claude Code run" + value: ${{ steps.claude.outputs.conclusion }} + +runs: + using: "composite" + steps: + - name: Clean up stale Claude locks + shell: bash + run: rm -rf ~/.claude/.locks ~/.local/state/claude/locks || true + + - name: Run Claude Code + id: claude + env: + GITHUB_TOKEN: ${{ inputs.github-token }} + uses: anthropics/claude-code-action@v1 + with: + github_token: ${{ inputs.github-token }} + claude_code_oauth_token: ${{ inputs.claude-oauth-token }} + bot_name: "Marvin Context Protocol" + trigger_phrase: ${{ inputs.trigger-phrase }} + allowed_bots: ${{ inputs.allowed-bots }} + track_progress: ${{ inputs.track-progress }} + prompt: ${{ inputs.prompt }} + claude_args: | + ${{ (inputs.allowed-tools != '' || inputs.extra-allowed-tools != '') && format('--allowedTools {0}{1}', inputs.allowed-tools, inputs.extra-allowed-tools != '' && format(',{0}', inputs.extra-allowed-tools) || '') || '' }} + ${{ inputs.mcp-servers != '' && format('--mcp-config ''{0}''', inputs.mcp-servers) || '' }} + --model ${{ inputs.model }} + settings: | + {"model": "${{ inputs.model }}"} diff --git a/.github/scripts/mention/gh-get-review-threads.sh b/.github/scripts/mention/gh-get-review-threads.sh new file mode 100755 index 000000000..2e1f4b35d --- /dev/null +++ b/.github/scripts/mention/gh-get-review-threads.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Get PR review threads with comments via GitHub GraphQL API +# +# Usage: +# gh-get-review-threads.sh [FILTER] +# +# Arguments: +# FILTER - Optional: filter for unresolved threads from specific author +# +# Environment (set by composite action): +# MENTION_REPO - Repository (owner/repo format) +# MENTION_PR_NUMBER - Pull request number +# GITHUB_TOKEN - GitHub API token +# +# Output: +# JSON array of review threads with nested comments + +# Parse OWNER and REPO from MENTION_REPO +REPO_FULL="${MENTION_REPO:?MENTION_REPO environment variable is required}" +OWNER="${REPO_FULL%/*}" +REPO="${REPO_FULL#*/}" +PR_NUMBER="${MENTION_PR_NUMBER:?MENTION_PR_NUMBER environment variable is required}" +FILTER="${1:-}" + +gh api graphql -f query=' + query($owner: String!, $repo: String!, $prNumber: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $prNumber) { + reviewThreads(first: 100) { + nodes { + id + isResolved + isOutdated + path + line + comments(first: 50) { + nodes { + id + body + author { login } + createdAt + } + } + } + } + } + } + }' -F owner="$OWNER" \ + -F repo="$REPO" \ + -F prNumber="$PR_NUMBER" \ + --jq '.data.repository.pullRequest.reviewThreads.nodes' | \ +if [ -n "$FILTER" ]; then + jq --arg author "$FILTER" ' + map(select( + .isResolved == false and + .comments.nodes | any(.author.login == $author) + ))' +else + cat +fi diff --git a/.github/scripts/mention/gh-resolve-review-thread.sh b/.github/scripts/mention/gh-resolve-review-thread.sh new file mode 100755 index 000000000..5dc08c239 --- /dev/null +++ b/.github/scripts/mention/gh-resolve-review-thread.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Resolve a GitHub PR review thread, optionally posting a comment first +# +# Usage: +# gh-resolve-review-thread.sh THREAD_ID [COMMENT] +# +# Arguments: +# THREAD_ID - The GraphQL node ID of the review thread to resolve +# COMMENT - Optional: Comment body to post before resolving +# +# Environment (set by composite action): +# MENTION_REPO - Repository (owner/repo format) +# MENTION_PR_NUMBER - Pull request number +# GITHUB_TOKEN - GitHub API token +# +# Behavior: +# 1. If COMMENT is provided, posts it as a reply to the thread +# 2. Resolves the thread + +# Validate required environment variables +: "${MENTION_REPO:?MENTION_REPO environment variable is required}" +: "${MENTION_PR_NUMBER:?MENTION_PR_NUMBER environment variable is required}" +THREAD_ID="${1:?Thread ID required}" +COMMENT="${2:-}" + +# Step 1: Post comment if provided +if [ -n "$COMMENT" ]; then + echo "Posting comment to thread..." >&2 + COMMENT_RESULT=$(gh api graphql -f query=' + mutation($threadId: ID!, $body: String!) { + addPullRequestReviewThreadReply(input: { + pullRequestReviewThreadId: $threadId, + body: $body + }) { + comment { + id + } + } + }' -f threadId="$THREAD_ID" -f body="$COMMENT") + if echo "$COMMENT_RESULT" | jq -e '.errors' > /dev/null 2>&1; then + echo "Error posting comment: $COMMENT_RESULT" >&2 + exit 1 + fi +fi + +# Step 2: Resolve the thread +echo "Resolving thread..." >&2 +RESOLVE_RESULT=$(gh api graphql -f query=' + mutation($threadId: ID!) { + resolveReviewThread(input: {threadId: $threadId}) { + thread { + id + isResolved + } + } + }' -f threadId="$THREAD_ID" --jq '.data.resolveReviewThread.thread') + +echo "$RESOLVE_RESULT" +echo "βœ“ Thread resolved" >&2 diff --git a/.github/scripts/pr-review/pr-comment.sh b/.github/scripts/pr-review/pr-comment.sh new file mode 100755 index 000000000..d571f6757 --- /dev/null +++ b/.github/scripts/pr-review/pr-comment.sh @@ -0,0 +1,251 @@ +#!/bin/bash +# pr-comment.sh - Queue a structured inline review comment for the PR review +# +# Usage: +# pr-comment.sh --severity --title --why [suggestion via stdin] +# pr-comment.sh --severity --title --why --no-suggestion +# +# Arguments: +# file File path (required) +# line Line number (required) +# --severity Severity level: critical, high, medium, low, nitpick (required) +# --title Brief description for comment heading (required) +# --why One sentence explaining the risk/impact (required) +# --no-suggestion Explicitly skip suggestion (use for architectural issues) +# +# The suggestion code is read from stdin (use heredoc). If no stdin and no --no-suggestion, errors. +# +# Examples: +# # With suggestion (preferred) +# pr-comment.sh src/main.go 42 --severity high --title "Missing error check" --why "Errors are silently ignored" <<'EOF' +# if err != nil { +# return fmt.Errorf("operation failed: %w", err) +# } +# EOF +# +# # Without suggestion (for issues requiring broader changes) +# pr-comment.sh src/main.go 42 --severity medium --title "Consider extracting to function" \ +# --why "This logic is duplicated in 3 places" --no-suggestion +# +# Environment variables (set by the composite action): +# PR_REVIEW_REPO - Repository (owner/repo) +# PR_REVIEW_PR_NUMBER - Pull request number +# PR_REVIEW_COMMENTS_DIR - Directory to cache comments (default: /tmp/pr-review-comments) + +set -e + +# Configuration from environment +REPO="${PR_REVIEW_REPO:?PR_REVIEW_REPO environment variable is required}" +PR_NUMBER="${PR_REVIEW_PR_NUMBER:?PR_REVIEW_PR_NUMBER environment variable is required}" +COMMENTS_DIR="${PR_REVIEW_COMMENTS_DIR:-/tmp/pr-review-comments}" + +# Severity emoji mapping +declare -A SEVERITY_EMOJI=( + [critical]="πŸ”΄ CRITICAL" + [high]="🟠 HIGH" + [medium]="🟑 MEDIUM" + [low]="βšͺ LOW" + [nitpick]="πŸ’¬ NITPICK" +) + +# Parse arguments +FILE="" +LINE="" +SEVERITY="" +TITLE="" +WHY="" +NO_SUGGESTION=false + +# First two positional args are file and line +if [ $# -lt 2 ]; then + echo "Error: file and line are required" + echo "Usage: pr-comment.sh --severity --title --why [<<'EOF' ... EOF]" + exit 1 +fi + +FILE="$1" +LINE="$2" +shift 2 + +# Parse named arguments +while [ $# -gt 0 ]; do + case "$1" in + --severity) + SEVERITY="$2" + shift 2 + ;; + --title) + TITLE="$2" + shift 2 + ;; + --why) + WHY="$2" + shift 2 + ;; + --no-suggestion) + NO_SUGGESTION=true + shift + ;; + *) + echo "Error: Unknown argument: $1" + exit 1 + ;; + esac +done + +# Read suggestion from stdin if available +SUGGESTION="" +if [ ! -t 0 ]; then + SUGGESTION=$(cat) +fi + +# Validate required arguments +if [ -z "$SEVERITY" ]; then + echo "Error: --severity is required (critical, high, medium, low, nitpick)" + exit 1 +fi + +if [ -z "$TITLE" ]; then + echo "Error: --title is required" + exit 1 +fi + +if [ -z "$WHY" ]; then + echo "Error: --why is required" + exit 1 +fi + +# Validate severity level +if [ -z "${SEVERITY_EMOJI[$SEVERITY]}" ]; then + echo "Error: Invalid severity '$SEVERITY'. Must be one of: critical, high, medium, low, nitpick" + exit 1 +fi + +# Require either suggestion or explicit --no-suggestion +if [ -z "$SUGGESTION" ] && [ "$NO_SUGGESTION" = false ]; then + echo "Error: Suggestion required. Provide code via stdin (heredoc) or use --no-suggestion" + echo "" + echo "Example with suggestion:" + echo " pr-comment.sh file.go 42 --severity high --title \"desc\" --why \"reason\" <<'EOF'" + echo " fixed code here" + echo " EOF" + echo "" + echo "Example without suggestion:" + echo " pr-comment.sh file.go 42 --severity medium --title \"desc\" --why \"reason\" --no-suggestion" + exit 1 +fi + +# Validate line is a positive integer (>= 1) +if ! [[ "$LINE" =~ ^[1-9][0-9]*$ ]]; then + echo "Error: Line number must be a positive integer (>= 1), got: $LINE" + exit 1 +fi + +# Get the diff for this file to validate the comment location +DIFF_DATA=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}/files" --paginate | jq --arg f "$FILE" '.[] | select(.filename==$f)') + +if [ -z "$DIFF_DATA" ]; then + echo "Error: File '${FILE}' not found in PR diff" + echo "" + echo "Files changed in this PR:" + gh api "repos/${REPO}/pulls/${PR_NUMBER}/files" --paginate --jq '.[].filename' + exit 1 +fi + +PATCH=$(echo "$DIFF_DATA" | jq -r '.patch // empty') + +if [ -z "$PATCH" ]; then + echo "Error: No patch data for file '${FILE}' (file may be binary or too large)" + exit 1 +fi + +# Verify the line exists in the diff +LINE_IN_DIFF=$(echo "$PATCH" | awk -v target_line="$LINE" ' +BEGIN { current_line = 0; found = 0 } +/^@@/ { + line = $0 + gsub(/.*\+/, "", line) + gsub(/[^0-9].*/, "", line) + current_line = line - 1 + next +} +{ + if (substr($0, 1, 1) != "-") { + current_line++ + if (current_line == target_line) { + found = 1 + exit + } + } +} +END { if (found) print "1"; else print "0" } +') + +if [ "$LINE_IN_DIFF" != "1" ]; then + echo "Error: Line ${LINE} not found in the diff for '${FILE}'" + echo "" + echo "Note: You can only comment on lines that appear in the diff (added, modified, or context lines)" + echo "" + echo "First 50 lines of diff for this file:" + echo "$PATCH" | head -50 + exit 1 +fi + +# Create comments directory if it doesn't exist +mkdir -p "${COMMENTS_DIR}" + +# Assemble the comment body +SEVERITY_LABEL="${SEVERITY_EMOJI[$SEVERITY]}" + +BODY="**${SEVERITY_LABEL}** ${TITLE} + +Why: ${WHY}" + +# Add suggestion block if provided +if [ -n "$SUGGESTION" ]; then + BODY="${BODY} + +\`\`\`suggestion +${SUGGESTION} +\`\`\`" +fi + +# Append standard footer +FOOTER=' + +--- +Marvin Context Protocol | Type `/marvin` to interact further + +Give us feedback! React with πŸš€ if perfect, πŸ‘ if helpful, πŸ‘Ž if not.' + +BODY_WITH_FOOTER="${BODY}${FOOTER}" + +# Generate unique comment ID +COMMENT_ID="comment-$(date +%s)-$(od -An -N4 -tu4 /dev/urandom | tr -d ' ')" +COMMENT_FILE="${COMMENTS_DIR}/${COMMENT_ID}.json" + +# Create the comment JSON object +jq -n \ + --arg path "$FILE" \ + --argjson line "$LINE" \ + --arg side "RIGHT" \ + --arg body "$BODY_WITH_FOOTER" \ + --arg id "$COMMENT_ID" \ + '{ + path: $path, + line: $line, + side: $side, + body: $body, + _meta: { + id: $id, + file: $path, + line: $line + } + }' > "${COMMENT_FILE}" + +echo "βœ“ Queued review comment for ${FILE}:${LINE}" +echo " Severity: ${SEVERITY_LABEL}" +echo " Title: ${TITLE}" +echo " Comment ID: ${COMMENT_ID}" +echo " Comment will be submitted with pr-review.sh" +echo " Remove with: pr-remove-comment.sh ${FILE} ${LINE}" diff --git a/.github/scripts/pr-review/pr-diff.sh b/.github/scripts/pr-review/pr-diff.sh new file mode 100755 index 000000000..4448e0012 --- /dev/null +++ b/.github/scripts/pr-review/pr-diff.sh @@ -0,0 +1,128 @@ +#!/bin/bash +# pr-diff.sh - Show changed files or diff for a specific file +# +# Usage: +# pr-diff.sh - List all changed files (shows full diff if small enough) +# pr-diff.sh - Show diff for a specific file with line numbers +# +# Environment variables (set by the composite action): +# PR_REVIEW_REPO - Repository (owner/repo) +# PR_REVIEW_PR_NUMBER - Pull request number + +set -e + +# Configuration from environment +REPO="${PR_REVIEW_REPO:?PR_REVIEW_REPO environment variable is required}" +PR_NUMBER="${PR_REVIEW_PR_NUMBER:?PR_REVIEW_PR_NUMBER environment variable is required}" +EXPECTED_HEAD="${PR_REVIEW_HEAD_SHA:-}" + +# Check if HEAD has changed since review started (race condition detection) +if [ -n "$EXPECTED_HEAD" ]; then + CURRENT_HEAD=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}" --jq '.head.sha') + if [ "$CURRENT_HEAD" != "$EXPECTED_HEAD" ]; then + echo "⚠️ WARNING: PR head has changed since review started!" + echo " Review started at: ${EXPECTED_HEAD:0:7}" + echo " Current head: ${CURRENT_HEAD:0:7}" + echo " Line numbers below may not match the commit being reviewed." + echo "" + fi +fi + +# Thresholds for "too big" - show file list only if exceeded +MAX_FILES=25 +MAX_TOTAL_LINES=1500 + +FILE="$1" + +# Function to add line numbers to a patch +# Format: [LINE] +added | [LINE] context | [----] -deleted +add_line_numbers() { + awk ' + BEGIN { new_line = 0 } + /^@@/ { + # Parse hunk header: @@ -old_start,old_count +new_start,new_count @@ + match($0, /\+([0-9]+)/) + new_line = substr($0, RSTART+1, RLENGTH-1) - 1 + print "" + print $0 + next + } + /^-/ { + # Deleted line - cannot comment on these + printf "[----] %s\n", $0 + next + } + /^\+/ { + # Added line - can comment, show line number + new_line++ + printf "[%4d] %s\n", new_line, $0 + next + } + { + # Context line (space prefix) - can comment, show line number + new_line++ + printf "[%4d] %s\n", new_line, $0 + } + ' +} + +if [ -z "$FILE" ]; then + # Get file list with stats + FILES_DATA=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}/files" --paginate) + + FILE_COUNT=$(echo "$FILES_DATA" | jq 'length') + TOTAL_ADDITIONS=$(echo "$FILES_DATA" | jq '[.[].additions] | add // 0') + TOTAL_DELETIONS=$(echo "$FILES_DATA" | jq '[.[].deletions] | add // 0') + TOTAL_LINES=$((TOTAL_ADDITIONS + TOTAL_DELETIONS)) + + echo "PR #${PR_NUMBER} Summary: ${FILE_COUNT} files changed (+${TOTAL_ADDITIONS}/-${TOTAL_DELETIONS})" + echo "" + + # Check if diff is too large + if [ "$FILE_COUNT" -gt "$MAX_FILES" ] || [ "$TOTAL_LINES" -gt "$MAX_TOTAL_LINES" ]; then + echo "⚠️ Large diff detected (>${MAX_FILES} files or >${MAX_TOTAL_LINES} lines changed)" + echo " Review files individually using: pr-diff.sh " + echo "" + echo "Files changed:" + echo "$FILES_DATA" | jq -r '.[] | " \(.filename) (+\(.additions)/-\(.deletions))"' + else + # Small enough - show all diffs with line numbers + echo "Files changed:" + echo "$FILES_DATA" | jq -r '.[] | " \(.filename) (+\(.additions)/-\(.deletions))"' + echo "" + echo "─────────────────────────────────────────────────────────────────────" + echo "" + + # Show each file's diff by iterating over indices + for i in $(seq 0 $((FILE_COUNT - 1))); do + FNAME=$(echo "$FILES_DATA" | jq -r ".[$i].filename") + PATCH=$(echo "$FILES_DATA" | jq -r ".[$i].patch // empty") + + if [ -n "$PATCH" ]; then + echo "## ${FNAME}" + echo "Use: pr-comment.sh ${FNAME} --severity --title \"desc\" --why \"reason\" <<'EOF' ... EOF" + echo "Format: [LINE] +added | [LINE] context | [----] -deleted (can't comment)" + echo "$PATCH" | add_line_numbers + echo "" + echo "─────────────────────────────────────────────────────────────────────" + echo "" + fi + done + fi +else + # Show specific file diff + PATCH=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}/files" --paginate --jq --arg file "$FILE" '.[] | select(.filename==$file) | .patch') + + if [ -z "$PATCH" ]; then + echo "Error: File '${FILE}' not found in PR diff" + echo "" + echo "Files changed in this PR:" + gh api "repos/${REPO}/pulls/${PR_NUMBER}/files" --paginate --jq '.[].filename' + exit 1 + fi + + echo "## ${FILE}" + echo "Use: pr-comment.sh ${FILE} --severity --title \"desc\" --why \"reason\" <<'EOF' ... EOF" + echo "Format: [LINE] +added | [LINE] context | [----] -deleted (can't comment)" + echo "$PATCH" | add_line_numbers +fi diff --git a/.github/scripts/pr-review/pr-existing-comments.sh b/.github/scripts/pr-review/pr-existing-comments.sh new file mode 100755 index 000000000..10fa05f16 --- /dev/null +++ b/.github/scripts/pr-review/pr-existing-comments.sh @@ -0,0 +1,190 @@ +#!/bin/bash +# pr-existing-comments.sh - Fetch existing review threads on a PR +# +# Usage: +# pr-existing-comments.sh - Show all review threads with full details +# pr-existing-comments.sh --summary - Show per-file summary only (for large PRs) +# pr-existing-comments.sh --unresolved - Show only unresolved threads +# pr-existing-comments.sh --file - Show threads for a specific file +# pr-existing-comments.sh --full - Show full comment text (no truncation) +# +# Output: Formatted summary of existing review threads grouped by file, +# showing thread status, comments, and whether issues were addressed. +# +# For large PRs, use --summary first to see the overview, then --file +# to get full thread details when reviewing each file. +# +# Environment variables (set by the composite action): +# PR_REVIEW_REPO - Repository (owner/repo) +# PR_REVIEW_PR_NUMBER - Pull request number + +set -e + +# Configuration from environment +REPO="${PR_REVIEW_REPO:?PR_REVIEW_REPO environment variable is required}" +PR_NUMBER="${PR_REVIEW_PR_NUMBER:?PR_REVIEW_PR_NUMBER environment variable is required}" + +OWNER="${REPO%/*}" +REPO_NAME="${REPO#*/}" + +# Parse arguments +FILTER_UNRESOLVED=false +FILTER_FILE="" +SUMMARY_ONLY=false +FULL_TEXT=false + +while [ $# -gt 0 ]; do + case "$1" in + --unresolved) + FILTER_UNRESOLVED=true + shift + ;; + --file) + FILTER_FILE="$2" + shift 2 + ;; + --summary) + SUMMARY_ONLY=true + shift + ;; + --full) + FULL_TEXT=true + shift + ;; + *) + echo "Usage: pr-existing-comments.sh [--summary] [--unresolved] [--file ] [--full]" + exit 1 + ;; + esac +done + +# Fetch review threads via GraphQL +THREADS=$(gh api graphql -f query=' + query($owner: String!, $repo: String!, $prNumber: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $prNumber) { + reviewThreads(first: 100) { + nodes { + id + isResolved + isOutdated + path + line + originalLine + startLine + originalStartLine + diffSide + comments(first: 50) { + nodes { + id + body + author { login } + createdAt + originalCommit { abbreviatedOid } + } + } + } + } + } + } + }' -F owner="$OWNER" \ + -F repo="$REPO_NAME" \ + -F prNumber="$PR_NUMBER" \ + --jq '.data.repository.pullRequest.reviewThreads.nodes') + +if [ -z "$THREADS" ] || [ "$THREADS" = "null" ]; then + echo "No existing review threads found." + exit 0 +fi + +# Apply filters +FILTERED="$THREADS" + +if [ "$FILTER_UNRESOLVED" = true ]; then + FILTERED=$(echo "$FILTERED" | jq '[.[] | select(.isResolved == false)]') +fi + +if [ -n "$FILTER_FILE" ]; then + FILTERED=$(echo "$FILTERED" | jq --arg file "$FILTER_FILE" '[.[] | select(.path == $file)]') +fi + +THREAD_COUNT=$(echo "$FILTERED" | jq 'length') + +if [ "$THREAD_COUNT" -eq 0 ]; then + if [ "$FILTER_UNRESOLVED" = true ]; then + echo "No unresolved review threads found." + elif [ -n "$FILTER_FILE" ]; then + echo "No review threads found for ${FILTER_FILE}." + else + echo "No existing review threads found." + fi + exit 0 +fi + +# Count resolved vs unresolved +RESOLVED_COUNT=$(echo "$FILTERED" | jq '[.[] | select(.isResolved == true)] | length') +UNRESOLVED_COUNT=$(echo "$FILTERED" | jq '[.[] | select(.isResolved == false)] | length') +OUTDATED_COUNT=$(echo "$FILTERED" | jq '[.[] | select(.isOutdated == true)] | length') + +echo "Existing review threads: ${THREAD_COUNT} total (${UNRESOLVED_COUNT} unresolved, ${RESOLVED_COUNT} resolved, ${OUTDATED_COUNT} outdated)" +echo "" + +# Summary mode: show per-file counts only +if [ "$SUMMARY_ONLY" = true ]; then + echo "Threads by file:" + echo "$FILTERED" | jq -r ' + group_by(.path) | .[] | + . as $threads | + ($threads | length) as $total | + ([$threads[] | select(.isResolved == false)] | length) as $unresolved | + ([$threads[] | select(.isResolved == true)] | length) as $resolved | + ([$threads[] | select(.isOutdated == true)] | length) as $outdated | + ([$threads[] | select(.comments.nodes | length > 1)] | length) as $has_replies | + " " + $threads[0].path + + " β€” " + ($total | tostring) + " threads" + + " (" + ($unresolved | tostring) + " unresolved, " + ($resolved | tostring) + " resolved" + + (if $outdated > 0 then ", " + ($outdated | tostring) + " outdated" else "" end) + + ")" + + (if $has_replies > 0 then " ⚠️ " + ($has_replies | tostring) + " with replies" else "" end) + ' + echo "" + echo "Use: pr-existing-comments.sh --file to see full thread details for a file" + exit 0 +fi + +# Full detail mode: output threads grouped by file +# Show full conversation for threads with replies +FIRST_LIMIT=200 +REPLY_LIMIT=300 +if [ "$FULL_TEXT" = true ]; then + FIRST_LIMIT=999999 + REPLY_LIMIT=999999 +fi + +echo "$FILTERED" | jq -r --argjson first_limit "$FIRST_LIMIT" --argjson reply_limit "$REPLY_LIMIT" ' + group_by(.path) | .[] | + "## " + .[0].path + " (" + (length | tostring) + " threads)\n" + + ([.[] | + " " + + (if .isResolved then "βœ… RESOLVED" elif .isOutdated then "⚠️ OUTDATED" else "πŸ”΄ UNRESOLVED" end) + + " (line " + (if .line then (.line | tostring) elif .startLine then (.startLine | tostring) elif .originalLine then ("~" + (.originalLine | tostring)) elif .originalStartLine then ("~" + (.originalStartLine | tostring)) else "?" end) + ")" + + # Show the commit the comment was originally made on + (if .comments.nodes[0].originalCommit.abbreviatedOid then " [" + .comments.nodes[0].originalCommit.abbreviatedOid + "]" else "" end) + + # Flag threads with replies β€” indicates a conversation happened + (if (.comments.nodes | length) > 1 then " ← has replies" else "" end) + + "\n" + + ([.comments.nodes | to_entries[] | + .value as $comment | + .key as $idx | + ($comment.body | gsub("\n"; " ")) as $flat | + if $idx == 0 then + " @" + ($comment.author.login // "unknown") + ": " + $flat[0:$first_limit] + + (if ($flat | length) > $first_limit then " [truncated]" else "" end) + else + " ↳ @" + ($comment.author.login // "unknown") + ": " + $flat[0:$reply_limit] + + (if ($flat | length) > $reply_limit then " [truncated]" else "" end) + end + ] | join("\n")) + + "\n" + ] | join("\n")) +' diff --git a/.github/scripts/pr-review/pr-remove-comment.sh b/.github/scripts/pr-review/pr-remove-comment.sh new file mode 100755 index 000000000..04b73fbf6 --- /dev/null +++ b/.github/scripts/pr-review/pr-remove-comment.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# pr-remove-comment.sh - Remove a queued review comment +# +# Usage: +# pr-remove-comment.sh +# pr-remove-comment.sh +# +# Examples: +# pr-remove-comment.sh src/main.go 42 +# pr-remove-comment.sh comment-1234567890-1234567890 +# +# This script removes a previously queued comment before it's submitted. +# Useful if the agent realizes it made a mistake or wants to update a comment. +# +# Environment variables (set by the composite action): +# PR_REVIEW_COMMENTS_DIR - Directory containing comment files (default: /tmp/pr-review-comments) + +set -e + +COMMENTS_DIR="${PR_REVIEW_COMMENTS_DIR:-/tmp/pr-review-comments}" + +if [ ! -d "${COMMENTS_DIR}" ]; then + echo "No comments directory found: ${COMMENTS_DIR}" + exit 0 +fi + +# Check if first argument looks like a comment ID +if [[ "$1" =~ ^comment- ]]; then + COMMENT_ID="$1" + COMMENT_FILE="${COMMENTS_DIR}/${COMMENT_ID}.json" + + if [ -f "${COMMENT_FILE}" ]; then + FILE=$(jq -r '._meta.file // .path' "${COMMENT_FILE}") + LINE=$(jq -r '._meta.line // .line' "${COMMENT_FILE}") + rm -f "${COMMENT_FILE}" + echo "βœ“ Removed comment ${COMMENT_ID} for ${FILE}:${LINE}" + else + echo "Comment not found: ${COMMENT_ID}" + exit 1 + fi +else + # Treat as file and line number + FILE="$1" + LINE="$2" + + if [ -z "$FILE" ] || [ -z "$LINE" ]; then + echo "Usage:" + echo " pr-remove-comment.sh " + echo " pr-remove-comment.sh " + echo "" + echo "Examples:" + echo " pr-remove-comment.sh src/main.go 42" + echo " pr-remove-comment.sh comment-1234567890-1234567890" + exit 1 + fi + + # Validate line is a positive integer (>= 1) + if ! [[ "$LINE" =~ ^[1-9][0-9]*$ ]]; then + echo "Error: Line number must be a positive integer (>= 1), got: $LINE" + exit 1 + fi + + # Find and remove matching comment files + # Use nullglob to handle case where no files match + shopt -s nullglob + REMOVED=0 + for COMMENT_FILE in "${COMMENTS_DIR}"/comment-*.json; do + + COMMENT_FILE_PATH=$(jq -r '._meta.file // .path' "${COMMENT_FILE}") + COMMENT_LINE=$(jq -r '._meta.line // .line' "${COMMENT_FILE}") + + if [ "$COMMENT_FILE_PATH" = "$FILE" ] && [ "$COMMENT_LINE" = "$LINE" ]; then + COMMENT_ID=$(basename "${COMMENT_FILE}" .json) + rm -f "${COMMENT_FILE}" + echo "βœ“ Removed comment ${COMMENT_ID} for ${FILE}:${LINE}" + REMOVED=$((REMOVED + 1)) + fi + done + + if [ "$REMOVED" -eq 0 ]; then + echo "No comment found for ${FILE}:${LINE}" + exit 1 + fi +fi diff --git a/.github/scripts/pr-review/pr-review.sh b/.github/scripts/pr-review/pr-review.sh new file mode 100755 index 000000000..48c0b6888 --- /dev/null +++ b/.github/scripts/pr-review/pr-review.sh @@ -0,0 +1,143 @@ +#!/bin/bash +# pr-review.sh - Submit a PR review (approve, request changes, or comment) +# +# Usage: pr-review.sh [review-body] +# Example: pr-review.sh REQUEST_CHANGES "Please fix the issues noted above" +# +# This script creates and submits a review with any queued inline comments. +# Comments are read from individual files in PR_REVIEW_COMMENTS_DIR (created by pr-comment.sh). +# +# The review body can contain special characters (backticks, dollar signs, etc.) +# and will be safely passed to the GitHub API without shell interpretation. +# +# Environment variables (set by the composite action): +# PR_REVIEW_REPO - Repository (owner/repo) +# PR_REVIEW_PR_NUMBER - Pull request number +# PR_REVIEW_HEAD_SHA - HEAD commit SHA +# PR_REVIEW_COMMENTS_DIR - Directory containing queued comment files (default: /tmp/pr-review-comments) + +set -e + +# Configuration from environment +REPO="${PR_REVIEW_REPO:?PR_REVIEW_REPO environment variable is required}" +PR_NUMBER="${PR_REVIEW_PR_NUMBER:?PR_REVIEW_PR_NUMBER environment variable is required}" +HEAD_SHA="${PR_REVIEW_HEAD_SHA:?PR_REVIEW_HEAD_SHA environment variable is required}" +COMMENTS_DIR="${PR_REVIEW_COMMENTS_DIR:-/tmp/pr-review-comments}" + +# Arguments +EVENT="$1" +shift 2>/dev/null || true + +# Read body from remaining arguments +# Join all remaining arguments with spaces, preserving the string as-is +BODY="$*" + +if [ -z "$EVENT" ]; then + echo "Usage: pr-review.sh [review-body]" + echo "Example: pr-review.sh REQUEST_CHANGES 'Please fix the issues noted in the inline comments'" + exit 1 +fi + +# Validate event type +case "$EVENT" in + APPROVE|REQUEST_CHANGES|COMMENT) + ;; + *) + echo "Error: Invalid event type '${EVENT}'" + echo "Must be one of: APPROVE, REQUEST_CHANGES, COMMENT" + exit 1 + ;; +esac + +# Read queued comments from individual files +COMMENTS="[]" +COMMENT_COUNT=0 + +if [ -d "${COMMENTS_DIR}" ]; then + # Collect all comment files and merge into a single JSON array + # Remove _meta fields before submitting (they're only for internal use) + COMMENT_FILES=("${COMMENTS_DIR}"/comment-*.json) + + if [ -f "${COMMENT_FILES[0]}" ]; then + # Use jq to read all comment files, extract the comment data (without _meta), and combine + COMMENTS=$(jq -s '[.[] | del(._meta)]' "${COMMENTS_DIR}"/comment-*.json) + COMMENT_COUNT=$(echo "$COMMENTS" | jq 'length') + if [ "$COMMENT_COUNT" -gt 0 ]; then + echo "Found ${COMMENT_COUNT} queued inline comment(s)" + fi + fi +fi + +# Append standard footer to the review body (if body is provided) +FOOTER=' + +--- +Marvin Context Protocol | Type `/marvin` to interact further + +Give us feedback! React with πŸš€ if perfect, πŸ‘ if helpful, πŸ‘Ž if not.' + +if [ -n "$BODY" ]; then + BODY_WITH_FOOTER="${BODY}${FOOTER}" +else + BODY_WITH_FOOTER="" +fi + +# Build the review request JSON +# Use jq to safely construct the JSON with all special characters handled +REVIEW_JSON=$(jq -n \ + --arg commit_id "$HEAD_SHA" \ + --arg event "$EVENT" \ + --arg body "$BODY_WITH_FOOTER" \ + --argjson comments "$COMMENTS" \ + '{ + commit_id: $commit_id, + event: $event, + comments: $comments + } + (if $body != "" then {body: $body} else {} end)') + +# Check if HEAD has changed since review started (race condition detection) +CURRENT_HEAD=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}" --jq '.head.sha') +if [ "$CURRENT_HEAD" != "$HEAD_SHA" ]; then + echo "⚠️ WARNING: PR head has changed since review started!" + echo " Review started at: ${HEAD_SHA:0:7}" + echo " Current head: ${CURRENT_HEAD:0:7}" + echo "" + echo " New commits may have shifted line numbers. Review will be submitted" + echo " against the original commit (${HEAD_SHA:0:7}) but comments may be outdated." + echo "" +fi + +echo "Submitting ${EVENT} review for commit ${HEAD_SHA:0:7}..." + +# Create and submit the review in one API call +# Use a temp file to safely pass the JSON body +TEMP_JSON=$(mktemp) +trap "rm -f ${TEMP_JSON}" EXIT +echo "$REVIEW_JSON" > "${TEMP_JSON}" + +RESPONSE=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}/reviews" \ + -X POST \ + --input "${TEMP_JSON}" 2>&1) || { + echo "Error submitting review:" + echo "$RESPONSE" + exit 1 +} + +# Clean up the comments directory after successful submission +if [ -d "${COMMENTS_DIR}" ] && [ "$COMMENT_COUNT" -gt 0 ]; then + rm -f "${COMMENTS_DIR}"/comment-*.json + # Remove directory if empty + rmdir "${COMMENTS_DIR}" 2>/dev/null || true +fi + +REVIEW_URL=$(echo "$RESPONSE" | jq -r '.html_url // empty') +REVIEW_STATE=$(echo "$RESPONSE" | jq -r '.state // empty') + +if [ -n "$REVIEW_URL" ]; then + echo "βœ“ Review submitted (${REVIEW_STATE}): ${REVIEW_URL}" + if [ "$COMMENT_COUNT" -gt 0 ]; then + echo " Included ${COMMENT_COUNT} inline comment(s)" + fi +else + echo "βœ“ Review submitted successfully" +fi diff --git a/.github/workflows/martian-issue-triage.yml b/.github/workflows/martian-issue-triage.yml deleted file mode 100644 index 276c7142e..000000000 --- a/.github/workflows/martian-issue-triage.yml +++ /dev/null @@ -1,178 +0,0 @@ -name: Martian Issue Triage - -on: - issues: - types: [opened, labeled] - -jobs: - martian-issue-triage: - # For labeled events, verify the labeler is a repo member to prevent privilege escalation - if: | - (github.event.action == 'opened' && contains(fromJSON('["strawgate", "jlowin"]'), github.actor)) || - (github.event.action == 'labeled' && github.event.label.name == 'triage-martian' && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.sender.author_association)) - - concurrency: - group: triage-martian-${{ github.event.issue.number }} - cancel-in-progress: true - - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - contents: read - issues: write - pull-requests: read - id-token: write - - steps: - - name: Checkout base repository - uses: actions/checkout@v6 - with: - repository: ${{ github.repository }} - ref: ${{ github.event.repository.default_branch }} - - # Install UV package manager - - name: Install UV - uses: astral-sh/setup-uv@v7 - with: - enable-cache: true - cache-dependency-glob: "uv.lock" - - - name: Generate Marvin App token - id: marvin-token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ secrets.MARVIN_APP_ID }} - private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }} - - - name: Set triage prompt - id: triage-prompt - run: | - cat >> $GITHUB_OUTPUT << 'EOF' - PROMPT< and tags. You shouldn't put everything in collapsible sections, especially if the response is short. Use your discretion to determine when to use collapsible sections to avoid overwhelming the reader with too much detail -- think of them like an appendix that can be expanded if the reader is interested. - - # Example output for "Recommendation" part of the response - PR #654 already implements the requested feature but is incomplete. The Pull Request is not in a mergeable state yet, the remaining work should be completed: 1) update the Calculator.divide method to utilize the new DivisionByZeroError or the safe_divide function, and 2) update the tests to ensure that the Calculator.divide method raises the new DivisionByZeroError when the divisor is 0. - -
- Findings - ...details from the code analysis that are relevant to the issue and the recommendation... -
- -
- Detailed Action Plan - ...a detailed plan that a junior developer could follow to implement the recommendation... -
- - # Example Output for "Related Items" part of the response - -
- Related Issues and Pull Requests - - | Repository | Issue or PR | Relevance | - | --- | --- | --- | - | jlowin/fastmcp | [Add matrix operations support](https://github.com/jlowin/fastmcp/pull/680) | This pull request directly addresses the feature request for adding matrix operations to the calculator. | - | jlowin/fastmcp | [Add matrix operations support](https://github.com/jlowin/fastmcp/issues/681) | This issue directly addresses the feature request for adding matrix operations to the calculator. | -
- -
- Related Files - - | Repository | File | Relevance | Sections | - | --- | --- | --- | --- | - | modelcontextprotocol/python-sdk | [test_calculator.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/test_calculator.py) | This file contains the test cases for the Calculator class, including a test that specifically asserts a ValueError is raised for division by zero, confirming the current intended behavior. | [25-27](https://github.com/modelcontextprotocol/python-sdk/blob/main/test_calculator.py#L25-L27) | - | modelcontextprotocol/python-sdk | [calculator.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/calculator.py) | This file contains the implementation of the Calculator class, specifically the `divide` method which raises the ValueError when dividing by zero, matching the bug report. | [29-32](https://github.com/modelcontextprotocol/python-sdk/blob/main/calculator.py#L29-L32) | -
- -
- Related Webpages - | Name | URL | Relevance | - | --- | --- | --- | - | Handling Division by Zero Best Practices | https://my-blog-about-division-by-zero.com/handling+division+by+zero+in+calculator | This webpage provides general best practices for handling division by zero in calculator applications and in Python, which is directly relevant to the issue and potential solutions. | -
- - PROMPT_END - EOF - - - name: Setup GitHub MCP Server - run: | - mkdir -p /tmp/mcp-config - cat > /tmp/mcp-config/mcp-servers.json << 'EOF' - { - "mcpServers": { - "repository-summary": { - "type": "http", - "url": "https://agents-md-generator.fastmcp.app/mcp" - }, - "code-search": { - "type": "http", - "url": "https://public-code-search.fastmcp.app/mcp" - }, - "github-research": { - "type": "stdio", - "command": "uvx", - "args": [ - "github-research-mcp" - ], - "env": { - "DISABLE_SUMMARIES": "true", - "GITHUB_PERSONAL_ACCESS_TOKEN": "${{ steps.marvin-token.outputs.token }}" - } - } - } - } - EOF - - - name: Clean up stale Claude locks - run: rm -rf ~/.claude/.locks ~/.local/state/claude/locks || true - - - name: Run Martian for Issue Triage - uses: anthropics/claude-code-action@v1 - with: - github_token: ${{ steps.marvin-token.outputs.token }} - bot_name: "Marvin Context Protocol" - prompt: ${{ steps.triage-prompt.outputs.PROMPT }} - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY_FOR_CI }} - track_progress: true - claude_args: | - --model claude-sonnet-4-5-20250929 - --allowedTools mcp__repository-summary,mcp__code-search__search_code,mcp__github-research__get_repository,mcp__github-research__get_issue,mcp__github-research__get_pull_request,mcp__github-research__search_issues,mcp__github-research__search_pull_requests,mcp__github-research__get_files - --mcp-config /tmp/mcp-config/mcp-servers.json - settings: | - { - "GH_TOKEN": "${{ steps.marvin-token.outputs.token }}" - } diff --git a/.github/workflows/martian-triage-issue.yml b/.github/workflows/martian-triage-issue.yml new file mode 100644 index 000000000..91317bc30 --- /dev/null +++ b/.github/workflows/martian-triage-issue.yml @@ -0,0 +1,204 @@ +# Triage new issues: investigate, recommend, apply labels +# Calls run-claude directly with triage prompt (elastic issue-triage style) + +name: Triage Issue + +on: + issues: + types: [opened] + +jobs: + triage: + if: | + contains(fromJSON('["jlowin", "strawgate"]'), github.event.issue.user.login) + concurrency: + group: triage-issue-${{ github.event.issue.number }} + cancel-in-progress: true + + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + issues: write + pull-requests: read + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + repository: ${{ github.repository }} + ref: ${{ github.event.repository.default_branch }} + + - name: Generate Marvin App token + id: marvin-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.MARVIN_APP_ID }} + private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }} + + - name: React to issue with eyes + env: + GH_TOKEN: ${{ steps.marvin-token.outputs.token }} + run: | + gh api "repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/reactions" -f content=eyes 2>/dev/null || true + + - name: Run Claude for Triage + uses: ./.github/actions/run-claude + with: + claude-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + github-token: ${{ steps.marvin-token.outputs.token }} + allowed-tools: "Edit,MultiEdit,Glob,Grep,LS,Read,Write,WebSearch,WebFetch,mcp__github_comment__update_claude_comment,mcp__github_ci__get_ci_status,mcp__github_ci__get_workflow_run_details,mcp__github_ci__download_job_log,Bash(*),mcp__agents-md-generator__generate_agents_md,mcp__public-code-search__search_code" + prompt: | + + Repository: ${{ github.repository }} + Issue Number: #${{ github.event.issue.number }} + Issue Title: ${{ github.event.issue.title }} + Issue Author: ${{ github.event.issue.user.login }} + + + + ${{ github.event.issue.body }} + + + + Triage this new GitHub issue and provide a helpful, actionable response. You can write files and execute commands to test, verify, or investigate the issue. + + + + This workflow is for investigation, testing, and planning. + + You CANNOT: Create branches, checkout branches, commit code to the repository + Do not push changes to the repository. + You CAN: Read/analyze code, search repository, review git history, search for similar issues, write files, verify behavior, provide analysis and recommendations + + + + You have access to the following tools (comma-separated list): + + Edit,MultiEdit,Glob,Grep,LS,Read,Write,WebSearch,WebFetch,mcp__github_comment__update_claude_comment,mcp__github_ci__get_ci_status,mcp__github_ci__get_workflow_run_details,mcp__github_ci__download_job_log,Bash(*),mcp__agents-md-generator__generate_agents_md,mcp__public-code-search__search_code + + You can only use tools that are explicitly listed above. For Bash commands, the pattern `Bash(command:*)` means you can run that command with any arguments. If a command is not listed, it is not available. + + + + Use `mcp__agents-md-generator__generate_agents_md` to get repository context before triaging. + + + + - `mcp__public-code-search__search_code`: Search code in OTHER repositories (use `Grep`/`Read` for this repo) + - `WebSearch`: Search the web for documentation, best practices, or solutions + - `WebFetch`: Fetch and read content from URLs + - Git commands: You have access to git commands, but write commands (commit, push, checkout, branch creation) are blocked + - Write: You can write files (e.g., test files, temporary files for verification) + - Execution: See `` section above for exact list of available execution commands + + + + If execution commands are available (check `` section), you can: + - Run tests to verify reported bugs or test proposed solutions + - Execute scripts to understand behavior + - Run linters or static analysis tools + - Verify environment setup or dependencies + - Test specific code paths or scenarios + - Write test files to confirm behavior + + When executing commands: + - Explain what you're testing and why + - Include command output in your response when relevant + - Use execution to validate your findings and recommendations + - Only use commands that are explicitly listed in `` + + + + Your number one priority is to provide a great response to the issue. A great response is a response that is clear, concise, accurate, and actionable. You will avoid long paragraphs, flowery language, and overly verbose responses. Your readers have limited time and attention, so you will be concise and to the point. + + In priority order your goal is to: + 1. Provide context about the request or issue (related issues, pull requests, files, etc.) + 2. Layout a single high-quality and actionable recommendation for how to address the issue based on your knowledge of the project, codebase, and issue + 3. Provide a high quality and detailed plan that a junior developer could follow to implement the recommendation + 4. Use execution to verify findings when appropriate (check `` section for available commands) + + + + Populate the following sections in your response: + Recommendation (or "No recommendation" with reason) + Findings + Verification (if you executed tests or commands - check `` section) + Detailed Action Plan + Related Items + Related Files + Related Webpages + + You may not be able to do all of these things, sometimes you may find that all you can do is provide in-depth context of the issue and related items. That's perfectly acceptable and expected. Your performance is judged by how accurate your findings are, do the investigation required to have high confidence in your findings and recommendations. "I don't know" or "I'm unable to recommend a course of action" is better than a bad or wrong answer. + + When formulating your response, you will never "bury the lede", you will always provide a clear and concise tl;dr as the first thing in your response. As your response grows in length you can organize the more detailed parts of your response collapsible sections using
and tags. You shouldn't put everything in collapsible sections, especially if the response is short. Use your discretion to determine when to use collapsible sections to avoid overwhelming the reader with too much detail -- think of them like an appendix that can be expanded if the reader is interested. + + + + # Example output for "Recommendation" part of the response + PR #654 already implements the requested feature but is incomplete. The Pull Request is not in a mergeable state yet, the remaining work should be completed: 1) update the Calculator.divide method to utilize the new DivisionByZeroError or the safe_divide function, and 2) update the tests to ensure that the Calculator.divide method raises the new DivisionByZeroError when the divisor is 0. + +
+ Findings + ...details from the code analysis that are relevant to the issue and the recommendation... +
+ +
+ Verification + I ran the existing tests (if execution commands are available in ``) and confirmed the current behavior: + ```bash + $ pytest test_calculator.py::test_divide_by_zero + FAILED - raises ValueError instead of DivisionByZeroError + ``` + This confirms the issue report is accurate. +
+ +
+ Detailed Action Plan + ...a detailed plan that a junior developer could follow to implement the recommendation... +
+ + # Example Output for "Related Items" part of the response + +
+ Related Issues and Pull Requests + + | Repository | Issue or PR | Relevance | + | --- | --- | --- | + | jlowin/fastmcp | [Add matrix operations support](https://github.com/jlowin/fastmcp/pull/680) | This pull request directly addresses the feature request for adding matrix operations to the calculator. | + | jlowin/fastmcp | [Add matrix operations support](https://github.com/jlowin/fastmcp/issues/681) | This issue directly addresses the feature request for adding matrix operations to the calculator. | +
+ +
+ Related Files + + | Repository | File | Relevance | Sections | + | --- | --- | --- | --- | + | modelcontextprotocol/python-sdk | [test_calculator.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/test_calculator.py) | This file contains the test cases for the Calculator class, including a test that specifically asserts a ValueError is raised for division by zero, confirming the current intended behavior. | [25-27](https://github.com/modelcontextprotocol/python-sdk/blob/main/test_calculator.py#L25-L27) | + | modelcontextprotocol/python-sdk | [calculator.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/calculator.py) | This file contains the implementation of the Calculator class, specifically the `divide` method which raises the ValueError when dividing by zero, matching the bug report. | [29-32](https://github.com/modelcontextprotocol/python-sdk/blob/main/calculator.py#L29-L32) | +
+ +
+ Related Webpages + + | Name | URL | Relevance | + | --- | --- | --- | + | Handling Division by Zero Best Practices | https://my-blog-about-division-by-zero.com/handling+division+by+zero+in+calculator | This webpage provides general best practices for handling division by zero in calculator applications and in Python, which is directly relevant to the issue and potential solutions. | +
+
+ + + Always end your comment with a new line, three dashes, and the footer message: + + + --- + Marvin Context Protocol | Type `/marvin` to interact further + + Give us feedback! React with πŸš€ if perfect, πŸ‘ if helpful, πŸ‘Ž if not. + + + + + When writing GitHub comments, wrap branch names, tags, or other @-references in backticks (e.g., `@main`, `@v1.0`) to avoid accidentally pinging users. Do not add backticks around terms that are already inside backticks or code blocks. + diff --git a/.github/workflows/marvin-comment-on-issue.yml b/.github/workflows/marvin-comment-on-issue.yml new file mode 100644 index 000000000..7c6a5ccf9 --- /dev/null +++ b/.github/workflows/marvin-comment-on-issue.yml @@ -0,0 +1,143 @@ +# Respond to /marvin mentions in issue comments (elastic mention-in-issue style) +# Calls run-claude directly + +name: Comment on Issue + +on: + issue_comment: + types: [created] + +permissions: + contents: write + issues: write + pull-requests: read + id-token: write + +jobs: + comment: + if: | + contains(github.event.comment.body, '/marvin') && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Install UV + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + + - name: Install dependencies + run: uv sync --python 3.12 + + - name: Run prek + uses: j178/prek-action@v1 + env: + SKIP: no-commit-to-branch + + - name: Generate Marvin App token + id: marvin-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.MARVIN_APP_ID }} + private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }} + + - name: React to comment with eyes + env: + GH_TOKEN: ${{ steps.marvin-token.outputs.token }} + run: | + gh api "repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions" -f content=eyes 2>/dev/null || true + + - name: Run Claude for Issue Comment + uses: ./.github/actions/run-claude + with: + claude-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + github-token: ${{ steps.marvin-token.outputs.token }} + trigger-phrase: "/marvin" + allowed-bots: "*" + allowed-tools: "Edit,MultiEdit,Glob,Grep,LS,Read,Write,WebSearch,WebFetch,mcp__github_comment__update_claude_comment,mcp__github_ci__get_ci_status,mcp__github_ci__get_workflow_run_details,mcp__github_ci__download_job_log,Bash(*),mcp__agents-md-generator__generate_agents_md,mcp__public-code-search__search_code" + prompt: | + + Repository: ${{ github.repository }} + Issue Number: #${{ github.event.issue.number }} + Issue Title: ${{ github.event.issue.title }} + Issue Author: ${{ github.event.issue.user.login }} + Comment Author: ${{ github.event.comment.user.login }} + + + + ${{ github.event.comment.body }} + + + + You have been mentioned in a GitHub issue comment. Understand the request, gather context, complete the task, and respond with results. + + + + This workflow allows read, write, and execute capabilities but cannot push changes. + + You CAN: Read/analyze code, modify files, write code, run tests, execute commands + You CANNOT: Commit code, push changes, create branches, checkout branches, create pull requests + + **Important**: You cannot push changes to the repository - you can only make changes locally and provide feedback or recommendations. + + + + You have access to the following tools (comma-separated list): + + Edit,MultiEdit,Glob,Grep,LS,Read,Write,WebSearch,WebFetch,mcp__github_comment__update_claude_comment,mcp__github_ci__get_ci_status,mcp__github_ci__get_workflow_run_details,mcp__github_ci__download_job_log,Bash(*),mcp__agents-md-generator__generate_agents_md,mcp__public-code-search__search_code + + You can only use tools that are explicitly listed above. For Bash commands, the pattern `Bash(command:*)` means you can run that command with any arguments. If a command is not listed, it is not available. + + + + Use `mcp__agents-md-generator__generate_agents_md` to get repository context before responding. + + + + Be thorough in your investigations: + - Understand the full context of the repository + - Review related code, issues, and PRs + - Consider edge cases and implications + - Gather all relevant information before responding + + Available tools: + - `mcp__public-code-search__search_code`: Search code in OTHER repositories (use `Grep`/`Read` for this repo) + - `WebSearch`: Search the web for documentation, best practices, or solutions + - `WebFetch`: Fetch and read content from URLs + + + + - Answer questions about the codebase + - Help debug reported problems (make changes locally to test, cannot push) + - Suggest solutions or workarounds + - Provide code examples + - Help clarify requirements + - Link to relevant documentation or code + + + + - Be concise and actionable + - If the request is unclear, ask clarifying questions + - If the request requires actions you cannot perform (like pushing changes), explain what you can and cannot do + - When making code changes, explain that they are local only and cannot be pushed + + + + Always end your comment with a new line, three dashes, and the footer message: + + + --- + Marvin Context Protocol | Type `/marvin` to interact further + + Give us feedback! React with πŸš€ if perfect, πŸ‘ if helpful, πŸ‘Ž if not. + + + + + When writing GitHub comments, wrap branch names, tags, or other @-references in backticks (e.g., `@main`, `@v1.0`) to avoid accidentally pinging users. Do not add backticks around terms that are already inside backticks or code blocks. + diff --git a/.github/workflows/marvin-comment-on-pr.yml b/.github/workflows/marvin-comment-on-pr.yml new file mode 100644 index 000000000..561d96ca2 --- /dev/null +++ b/.github/workflows/marvin-comment-on-pr.yml @@ -0,0 +1,294 @@ +# Respond to /marvin mentions in PR comments or PR body (elastic mention-in-pr style) +# Calls run-claude directly + +name: Comment on PR + +on: + pull_request_review_comment: + types: [created] + pull_request: + types: [opened, edited] + +permissions: + contents: write + pull-requests: write + issues: read + id-token: write + +jobs: + comment: + if: | + (github.event_name == 'pull_request_review_comment' && + contains(github.event.comment.body, '/marvin') && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)) || + (github.event_name == 'pull_request' && + contains(github.event.pull_request.body, '/marvin') && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association)) + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout PR head branch + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Install UV + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + + - name: Install dependencies + run: uv sync --python 3.12 + + - name: Run prek + uses: j178/prek-action@v1 + env: + SKIP: no-commit-to-branch + + - name: Generate Marvin App token + id: marvin-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.MARVIN_APP_ID }} + private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }} + + - name: React to comment with eyes + if: github.event_name == 'pull_request_review_comment' + env: + GH_TOKEN: ${{ steps.marvin-token.outputs.token }} + run: | + gh api "repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions" -f content=eyes 2>/dev/null || true + + - name: React to PR with eyes + if: github.event_name == 'pull_request' + env: + GH_TOKEN: ${{ steps.marvin-token.outputs.token }} + run: | + gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/reactions" -f content=eyes 2>/dev/null || true + + - name: Get PR HEAD SHA + id: pr-info + env: + GH_TOKEN: ${{ steps.marvin-token.outputs.token }} + run: | + HEAD_SHA=$(gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}" --jq '.head.sha') + echo "head_sha=${HEAD_SHA}" >> "$GITHUB_OUTPUT" + + - name: Run Claude for PR Comment + uses: ./.github/actions/run-claude + env: + MENTION_REPO: ${{ github.repository }} + MENTION_PR_NUMBER: ${{ github.event.pull_request.number }} + MENTION_SCRIPTS: ${{ github.workspace }}/.github/scripts/mention + PR_REVIEW_REPO: ${{ github.repository }} + PR_REVIEW_PR_NUMBER: ${{ github.event.pull_request.number }} + PR_REVIEW_HEAD_SHA: ${{ steps.pr-info.outputs.head_sha }} + PR_REVIEW_COMMENTS_DIR: /tmp/pr-review-comments + PR_REVIEW_HELPERS_DIR: ${{ github.workspace }}/.github/scripts/pr-review + with: + claude-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + github-token: ${{ steps.marvin-token.outputs.token }} + trigger-phrase: "/marvin" + allowed-bots: "*" + allowed-tools: "Edit,MultiEdit,Glob,Grep,LS,Read,Write,WebSearch,WebFetch,mcp__github_comment__update_claude_comment,mcp__github_ci__get_ci_status,mcp__github_ci__get_workflow_run_details,mcp__github_ci__download_job_log,Bash(*),mcp__agents-md-generator__generate_agents_md,mcp__public-code-search__search_code" + prompt: | + + Repository: ${{ github.repository }} + PR Number: #${{ github.event.pull_request.number }} + PR Title: ${{ github.event.pull_request.title }} + PR Author: ${{ github.event.pull_request.user.login }} + Comment Author: ${{ github.event.comment.user.login || github.event.pull_request.user.login }} + + **Note**: The PR head branch has already been checked out. The workspace is ready - you can immediately start working on the PR code. + + + + ${{ github.event.comment.body || github.event.pull_request.body }} + + + + You have been mentioned in a Pull Request comment. Understand the request, gather context, complete the task, and respond with results. + + + + This workflow allows read, write, and execute capabilities but cannot push changes. + + You CAN: Read/analyze code, modify files, write code, run tests, execute commands, resolve review threads + You CANNOT: Commit code, push changes, create branches, checkout branches, create pull requests + + **Important**: You cannot push changes to the repository - you can only make changes locally and provide feedback or recommendations. + + + + You have access to the following tools (comma-separated list): + + Edit,MultiEdit,Glob,Grep,LS,Read,Write,WebSearch,WebFetch,mcp__github_comment__update_claude_comment,mcp__github_ci__get_ci_status,mcp__github_ci__get_workflow_run_details,mcp__github_ci__download_job_log,Bash(*),mcp__agents-md-generator__generate_agents_md,mcp__public-code-search__search_code + + You can only use tools that are explicitly listed above. For Bash commands, the pattern `Bash(command:*)` means you can run that command with any arguments. If a command is not listed, it is not available. + + + + Use `mcp__agents-md-generator__generate_agents_md` to get repository context before responding. + + + + Be thorough in your investigations: + - Understand the full context of the repository + - Review related code, issues, and PRs + - Consider edge cases and implications + - Gather all relevant information before responding + + Available tools: + - `mcp__public-code-search__search_code`: Search code in OTHER repositories (use `Grep`/`Read` for this repo) + - `WebSearch`: Search the web for documentation, best practices, or solutions + - `WebFetch`: Fetch and read content from URLs + + + + - Address review feedback and fix issues (make changes locally, cannot push) + - Answer questions about the changes + - Make additional code changes (local only) + - Resolve review threads after addressing feedback (if changes are made separately) + - Perform PR reviews when asked (use the PR review process below) + + + + When asked to review this PR, follow this structured review process. + The `$PR_REVIEW_HELPERS_DIR` environment variable is pre-configured for all scripts below. + + + Follow these steps in order: + + **Step 1: Gather context** + - Use `mcp__agents-md-generator__generate_agents_md` to get repository context + (if this fails, explore the repository to understand the codebase β€” read key files like README, CONTRIBUTING, etc.) + - Run `$PR_REVIEW_HELPERS_DIR/pr-existing-comments.sh --summary` to see existing review threads per file + - Run `$PR_REVIEW_HELPERS_DIR/pr-diff.sh` to see changed files with line-numbered diffs + (for large PRs, this lists files only β€” review each with `pr-diff.sh `) + + **Step 2: Review each file** + For each changed file: + a. If the summary showed existing threads for this file, first run: + `$PR_REVIEW_HELPERS_DIR/pr-existing-comments.sh --file ` + Read the full thread details. The output uses these conventions: + - `← has replies` β€” a conversation happened; read carefully before commenting + - `[truncated]` β€” comment was cut short; add `--full` if you need the complete text to understand the comment + - `[abc1234]` β€” commit the comment was made on; use `git show abc1234` if needed + - `~42` β€” approximate line from an older revision (exact line no longer maps to current diff) + b. Review the diff. Use `Read` to see full file contents when you need more context. + Identify issues matching review_criteria. Do NOT flag: + - Issues in unchanged code (only review the diff) + - Style preferences handled by linters + - Pre-existing issues not introduced by this PR + - Issues already covered by existing threads (see below) + + **Existing thread rules** (check BEFORE leaving any comment): + - Resolved with reviewer reply β†’ reviewer's decision is final. Do NOT re-flag. + Examples: "It should remain as X", "This is intentional", "No need to do this change" + - Resolved without reply β†’ author likely fixed it. Do NOT re-raise unless the fix introduced a new problem. + - Unresolved β†’ already flagged. Do NOT re-comment. Mention in review body if you have more to add. + - Outdated β†’ code changed. Only re-flag if the issue still applies to the current diff. + When in doubt, do not duplicate. Redundant comments erode trust in the review process. + + **Step 3: Leave comments for NEW issues only** + For each genuinely new issue not covered by existing threads: + ```bash + $PR_REVIEW_HELPERS_DIR/pr-comment.sh \ + --severity \ + --title "Brief description" \ + --why "Risk or impact" <<'EOF' + corrected code here + EOF + ``` + Always provide suggestion code. Use `--no-suggestion` only when the fix requires + changes across multiple locations. Broader architectural concerns belong in the + review body, not inline comments. + + To remove a queued comment: `$PR_REVIEW_HELPERS_DIR/pr-remove-comment.sh ` + + **Step 4: Submit the review** + ```bash + $PR_REVIEW_HELPERS_DIR/pr-review.sh "" + ``` + - REQUEST_CHANGES: Any πŸ”΄ CRITICAL or 🟠 HIGH issues found + - COMMENT: 🟑 MEDIUM issues found (but no critical/high) + - APPROVE: No issues, or only βšͺ LOW / πŸ’¬ NITPICK suggestions + + The review body should include broader architectural concerns not suited for inline comments. + Avoid summarizing the PR or offering praise. If approving with no issues, omit the review body. + A standard footer is automatically appended to all comments and reviews. + + + + πŸ”΄ CRITICAL - Must fix before merge (security vulnerabilities, data corruption, production-breaking bugs) + 🟠 HIGH - Should fix before merge (logic errors, missing validation, significant performance issues) + 🟑 MEDIUM - Address soon, non-blocking (error handling gaps, suboptimal patterns, missing edge cases) + βšͺ LOW - Author discretion, non-blocking (minor improvements, documentation, style not covered by linters) + πŸ’¬ NITPICK - Truly optional (stylistic preferences, alternative approaches β€” safe to ignore) + + + + Focus on these categories, in priority order: + 1. Security vulnerabilities (injection, XSS, auth bypass, secrets exposure) + 2. Logic bugs that could cause runtime failures or incorrect behavior + 3. Data integrity issues (race conditions, missing transactions, corruption risk) + 4. Performance bottlenecks (N+1 queries, memory leaks, blocking operations) + 5. Error handling gaps (unhandled exceptions, missing validation) + 6. Breaking changes to public APIs without migration path + 7. Missing or incorrect test coverage for critical paths + + + + + View unresolved review threads: + ```bash + $MENTION_SCRIPTS/gh-get-review-threads.sh + ``` + + Filter for unresolved threads from a specific reviewer: + ```bash + $MENTION_SCRIPTS/gh-get-review-threads.sh "reviewer-username" + ``` + + Resolve a review thread after addressing feedback: + ```bash + $MENTION_SCRIPTS/gh-resolve-review-thread.sh "THREAD_ID" "Fixed by updating the error handling" + ``` + - `THREAD_ID` is the GraphQL node ID from the review threads output (e.g., `PRRT_kwDOABC123`) + - The comment is optional - use it to explain what you did + + Note: Since you cannot push changes, you can resolve threads to acknowledge feedback, but actual fixes would need to be applied separately. + + + + - Be concise and actionable + - If the request is unclear, ask clarifying questions + - If the request requires actions you cannot perform (like pushing changes), explain what you can and cannot do + - When making code changes, explain that they are local only and cannot be pushed + + **When performing a PR review**: Your substantive feedback belongs in the PR review submission + (via pr-review.sh), not in the comment response. The comment should only report: + - That you've submitted the review (with the outcome: approved, requested changes, etc.) + - Any issues encountered during the review process + - Brief status updates + + Do NOT duplicate the review content in your comment - the review itself contains all the details. + Keep the comment short, e.g., "I've submitted my review requesting changes. See the review for details." + + + + Always end your comment with a new line, three dashes, and the footer message: + + + --- + Marvin Context Protocol | Type `/marvin` to interact further + + Give us feedback! React with πŸš€ if perfect, πŸ‘ if helpful, πŸ‘Ž if not. + + + + + When writing GitHub comments, wrap branch names, tags, or other @-references in backticks (e.g., `@main`, `@v1.0`) to avoid accidentally pinging users. Do not add backticks around terms that are already inside backticks or code blocks. + diff --git a/.github/workflows/marvin.yml b/.github/workflows/marvin.yml deleted file mode 100644 index ba394207d..000000000 --- a/.github/workflows/marvin.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: Marvin Context Protocol - -on: - issue_comment: { types: [created] } - pull_request_review_comment: { types: [created] } - pull_request_review: { types: [submitted] } - pull_request: { types: [opened, edited] } - issues: { types: [opened, edited, assigned, labeled] } - discussion: { types: [created, edited, labeled] } - discussion_comment: { types: [created] } - -permissions: - contents: write - issues: write - pull-requests: write - discussions: write - actions: read - id-token: write - -jobs: - marvin: - # Restrict all triggers to repo members (OWNER, MEMBER, COLLABORATOR) - if: | - ( - (github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion_comment') && - contains(github.event.comment.body, '/marvin') && - contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) - ) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '/marvin') && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.review.author_association)) || - (github.event_name == 'pull_request' && contains(github.event.pull_request.body, '/marvin') && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association)) || - (github.event_name == 'issues' && contains(github.event.issue.body, '/marvin') && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.issue.author_association)) || - (github.event_name == 'discussion' && contains(github.event.discussion.body, '/marvin') && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.discussion.author_association)) || - ( - github.event_name == 'issues' && - ((github.event.action == 'assigned' && github.event.assignee.login == 'Marvin Context Protocol') || (github.event.action == 'labeled' && github.event.label.name == 'marvin')) && - contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.sender.author_association) - ) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - # Install UV package manager - - name: Install UV - uses: astral-sh/setup-uv@v7 - with: - enable-cache: true - cache-dependency-glob: "uv.lock" - - # Install project dependencies - - name: Install dependencies - run: uv sync --python 3.12 - - - name: Run prek - uses: j178/prek-action@v1 - env: - SKIP: no-commit-to-branch - - - name: Generate Marvin App token - id: marvin-token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ secrets.MARVIN_APP_ID }} - private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }} - - - name: Clean up stale Claude locks - run: rm -rf ~/.claude/.locks ~/.local/state/claude/locks || true - - # Marvin Assistant - - name: Run Marvin - uses: anthropics/claude-code-action@v1 - with: - github_token: ${{ steps.marvin-token.outputs.token }} - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - trigger_phrase: "/marvin" - allowed_bots: "*" - claude_args: | - --allowedTools WebSearch,WebFetch,Bash(uv:*),Bash(pre-commit:*),Bash(prek:*),Bash(pytest:*),Bash(ruff:*),Bash(ty:*),Bash(git:*),Bash(gh:*),mcp__github__add_issue_comment,mcp__github__create_issue,mcp__github__get_issue,mcp__github__list_issues,mcp__github__search_issues,mcp__github__update_issue,mcp__github__update_issue_comment,mcp__github__create_pull_request,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_files,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__list_pull_requests,mcp__github__update_pull_request,mcp__github__update_pull_request_branch,mcp__github__update_pull_request_comment,mcp__github__merge_pull_request - additional_permissions: | - actions: read - settings: | - { - "model": "claude-sonnet-4-5-20250929", - "env": { - "GH_TOKEN": "${{ steps.marvin-token.outputs.token }}" - }, - "customInstructions": "When you complete work on an issue: (1) You MUST create a pull request using the mcp__github__create_pull_request tool instead of posting a link, and (2) You MUST add the 'marvin-pr' label to the original issue using mcp__github__update_issue. Even if PR creation fails and you post a link instead, you MUST still add the 'marvin-pr' label. Follow the PR message guidelines in CLAUDE.md." - }