From 4728706e3bd91683829062f580a8e029d06ffc78 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 6 Jul 2026 01:19:15 +0200 Subject: [PATCH] feat(coding-agent): trigger issue analysis from comments --- .github/workflows/issue-analysis.yml | 69 ++++++++++++++++++++++++---- .pi/extensions/import-repro.ts | 65 ++++++++++++++++++++++---- 2 files changed, 116 insertions(+), 18 deletions(-) diff --git a/.github/workflows/issue-analysis.yml b/.github/workflows/issue-analysis.yml index cfa5a917..3b79f668 100644 --- a/.github/workflows/issue-analysis.yml +++ b/.github/workflows/issue-analysis.yml @@ -1,4 +1,5 @@ -# Runs the repo's /is prompt against an issue when the `pi-analyze` label is added. +# Runs the repo's /is prompt against an issue when the `pi-analyze` label is added +# or when a staff member comments `@issueron analyze` on an issue. # # Setup required before this works: # 1. Create a `pi-analyze` GitHub environment on the repo and add a @@ -14,14 +15,16 @@ # # The session runs in a high-entropy checkout directory so the recorded cwd is # a unique string. Import the session into a local checkout with the -# import-repro extension (.pi/extensions/import-repro.ts): -# pi "/import-repro " +# /ir extension command (.pi/extensions/import-repro.ts): +# pi "/ir " name: Issue Analysis on: issues: types: [labeled] + issue_comment: + types: [created] permissions: contents: read @@ -33,24 +36,55 @@ concurrency: jobs: authorize: - if: github.event.label.name == 'pi-analyze' runs-on: ubuntu-latest + outputs: + should_run: ${{ steps.verify.outputs.should_run }} + extra_instructions: ${{ steps.verify.outputs.extra_instructions }} steps: - name: Verify sender permission + id: verify uses: actions/github-script@v7 env: ORG_READ_TOKEN: ${{ secrets.EARENDIL_ORG_READ_TOKEN }} with: script: | + const ANALYZE_LABEL = 'pi-analyze'; const username = context.payload.sender.login; + let extraInstructions = ''; + + core.setOutput('should_run', 'false'); + core.setOutput('extra_instructions', ''); + + if (context.eventName === 'issues') { + if (context.payload.action !== 'labeled' || context.payload.label?.name !== ANALYZE_LABEL) { + console.log('Not a pi-analyze label event'); + return; + } + } else if (context.eventName === 'issue_comment') { + if (context.payload.issue.pull_request) { + console.log('Ignoring pull request comment'); + return; + } + const body = context.payload.comment.body || ''; + const match = body.match(/^\s*@issueron\s+analyze\b([\s\S]*)$/i); + if (!match) { + console.log('Comment is not an @issueron analyze trigger'); + return; + } + extraInstructions = match[1].trim(); + } else { + console.log(`Unsupported event: ${context.eventName}`); + return; + } async function removeTriggerLabel() { + if (context.eventName !== 'issues') return; try { await github.rest.issues.removeLabel({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, - name: 'pi-analyze', + name: ANALYZE_LABEL, }); } catch (error) { if (error.status !== 404) throw error; @@ -117,11 +151,24 @@ jobs: core.setFailed( `@${username} has '${data.permission}' permission; write or admin is required to trigger issue analysis.`, ); + return; } + if (context.eventName === 'issue_comment') { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: [ANALYZE_LABEL], + }); + } + + core.setOutput('should_run', 'true'); + core.setOutput('extra_instructions', extraInstructions); + analyze: needs: authorize - if: needs.authorize.result == 'success' + if: needs.authorize.outputs.should_run == 'true' runs-on: ubuntu-latest environment: pi-analyze timeout-minutes: 45 @@ -173,14 +220,20 @@ jobs: PI_CODING_AGENT_DIR: ${{ runner.temp }}/pi-agent GH_TOKEN: ${{ github.token }} ISSUE_URL: ${{ github.event.issue.html_url }} + EXTRA_INSTRUCTIONS: ${{ needs.authorize.outputs.extra_instructions }} run: | mkdir -p "$RUNNER_TEMP/pi-out/session" + prompt="/is $ISSUE_URL" + if [ -n "$EXTRA_INSTRUCTIONS" ]; then + prompt+=$'\n\nAdditional instructions from @issueron analyze comment:\n' + prompt+="$EXTRA_INSTRUCTIONS" + fi ./pi-test.sh \ -p \ --approve \ --session-dir "$RUNNER_TEMP/pi-out/session" \ --model "$ISSUE_ANALYSIS_MODEL" \ - "/is $ISSUE_URL" | tee "$RUNNER_TEMP/pi-out/output.md" + "$prompt" | tee "$RUNNER_TEMP/pi-out/output.md" - name: Export session files id: export_session_files @@ -236,7 +289,7 @@ jobs: 'Continue locally from a checkout with:', '', '```sh', - `pi "/import-repro ${gistId}"`, + `pi "/ir ${gistId}"`, '```', ].join('\n'); diff --git a/.pi/extensions/import-repro.ts b/.pi/extensions/import-repro.ts index 21da958e..a0096f61 100644 --- a/.pi/extensions/import-repro.ts +++ b/.pi/extensions/import-repro.ts @@ -7,11 +7,12 @@ * current session directory, and switches to it. * * Usage: - * /import-repro b4d100022aefb12f25dd2d8485e0a82a - * /import-repro https://gist.github.com/mitsuhiko/b4d100022aefb12f25dd2d8485e0a82a - * /import-repro https://pi.dev/session/#b4d100022aefb12f25dd2d8485e0a82a + * /ir b4d100022aefb12f25dd2d8485e0a82a + * /ir https://gist.github.com/mitsuhiko/b4d100022aefb12f25dd2d8485e0a82a + * /ir https://pi.dev/session/#b4d100022aefb12f25dd2d8485e0a82a + * /ir https://github.com/earendil-works/pi/issues/123 * - * pi "/import-repro " + * pi "/ir " */ import { Buffer } from "node:buffer"; @@ -22,6 +23,8 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-c const GIST_ID_RE = /^[0-9a-fA-F]{20,}$/; const GIST_URL_RE = /^https:\/\/gist\.github\.com\/(?:[^/]+\/)?([0-9a-fA-F]{20,})(?:[/#?].*)?$/; const SHARE_URL_RE = /^https:\/\/pi\.dev\/session\/#([0-9a-fA-F]{20,})(?:[/#?].*)?$/; +const ISSUE_URL_RE = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)(?:[/#?].*)?$/; +const GIST_URL_IN_TEXT_RE = /https:\/\/gist\.github\.com\/(?:[^/\s]+\/)?([0-9a-fA-F]{20,})\b/g; const SESSION_DATA_RE = /