feat: update recording, transcript endpoint and add tests #14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Cubic AI to Devin Review | |
| on: | |
| pull_request_review: | |
| types: [submitted] | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| jobs: | |
| trigger-devin: | |
| name: Trigger Devin to Address Cubic AI Review | |
| # Only run if the review is from cubic-dev-ai bot and contains the AI prompt | |
| if: github.event.review.user.login == 'cubic-dev-ai[bot]' | |
| runs-on: blacksmith-2vcpu-ubuntu-2404 | |
| steps: | |
| - name: Extract AI prompt from Cubic review | |
| id: extract-prompt | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const reviewBody = context.payload.review.body || ''; | |
| // Look for the AI prompt section in Cubic's review | |
| // Format: <details><summary>Prompt for AI agents (all issues)</summary> | |
| // ```text | |
| // ... prompt content ... | |
| // ``` | |
| // </details> | |
| const promptMatch = reviewBody.match(/<details>\s*<summary>Prompt for AI agents[^<]*<\/summary>\s*```(?:text)?\s*([\s\S]*?)```\s*<\/details>/i); | |
| if (!promptMatch || !promptMatch[1]) { | |
| console.log('No AI prompt found in review body'); | |
| console.log('Review body:', reviewBody); | |
| core.setOutput('has-prompt', 'false'); | |
| return; | |
| } | |
| const prompt = promptMatch[1].trim(); | |
| console.log('Extracted prompt:', prompt); | |
| // Save prompt to file to avoid shell escaping issues | |
| const fs = require('fs'); | |
| fs.writeFileSync('/tmp/cubic-prompt.txt', prompt); | |
| core.setOutput('has-prompt', 'true'); | |
| - name: Check for existing Devin session | |
| if: steps.extract-prompt.outputs.has-prompt == 'true' | |
| id: check-session | |
| uses: actions/github-script@v7 | |
| env: | |
| DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }} | |
| with: | |
| script: | | |
| async function checkSessionStatus(sessionId) { | |
| const response = await fetch(`https://api.devin.ai/v1/sessions/${sessionId}`, { | |
| headers: { | |
| 'Authorization': `Bearer ${process.env.DEVIN_API_KEY}`, | |
| 'Content-Type': 'application/json' | |
| } | |
| }); | |
| if (!response.ok) { | |
| console.log(`Failed to fetch session ${sessionId}: ${response.status}`); | |
| return null; | |
| } | |
| return await response.json(); | |
| } | |
| // If PR was created through Devin, always use that session | |
| const prBody = context.payload.pull_request.body || ''; | |
| const prSessionMatch = prBody.match(/app\.devin\.ai\/sessions\/([a-f0-9-]+)/); | |
| if (prSessionMatch) { | |
| const sessionId = prSessionMatch[1]; | |
| console.log(`PR was created by Devin session: ${sessionId}`); | |
| const session = await checkSessionStatus(sessionId); | |
| if (session) { | |
| console.log(`PR creator session status: ${session.status_enum}`); | |
| core.exportVariable('EXISTING_SESSION_ID', sessionId); | |
| core.exportVariable('SESSION_URL', `https://app.devin.ai/sessions/${sessionId}`); | |
| core.setOutput('has-active-session', 'true'); | |
| return; | |
| } | |
| } | |
| // PR was not created through Devin - check for existing Devin session from PR comments | |
| // Look for any Devin session comment (cubic review, conflict resolution, stale PR completion, etc.) | |
| const devinSessionPatterns = [ | |
| 'Devin AI is addressing Cubic AI', | |
| 'Devin AI is resolving merge conflicts', | |
| 'Devin AI is completing this stale PR' | |
| ]; | |
| const comments = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.payload.pull_request.number | |
| }); | |
| let sessionId = null; | |
| for (const comment of comments.data.reverse()) { | |
| const hasDevinSession = devinSessionPatterns.some(pattern => comment.body.includes(pattern)); | |
| if (hasDevinSession) { | |
| const match = comment.body.match(/app\.devin\.ai\/sessions\/([a-f0-9-]+)/); | |
| if (match) { | |
| sessionId = match[1]; | |
| console.log(`Found existing Devin session from comment: ${sessionId}`); | |
| break; | |
| } | |
| } | |
| } | |
| if (!sessionId) { | |
| console.log('No existing Devin session found in PR comments'); | |
| core.setOutput('has-active-session', 'false'); | |
| return; | |
| } | |
| console.log(`Found session ID from comment: ${sessionId}`); | |
| const session = await checkSessionStatus(sessionId); | |
| if (!session) { | |
| core.setOutput('has-active-session', 'false'); | |
| return; | |
| } | |
| const activeStatuses = ['working', 'blocked', 'resumed']; | |
| if (activeStatuses.includes(session.status_enum)) { | |
| console.log(`Session ${sessionId} is active (status: ${session.status_enum})`); | |
| core.exportVariable('EXISTING_SESSION_ID', sessionId); | |
| core.exportVariable('SESSION_URL', `https://app.devin.ai/sessions/${sessionId}`); | |
| core.setOutput('has-active-session', 'true'); | |
| } else { | |
| console.log(`Session ${sessionId} is not active (status: ${session.status_enum})`); | |
| core.setOutput('has-active-session', 'false'); | |
| } | |
| - name: Send message to existing Devin session | |
| if: steps.extract-prompt.outputs.has-prompt == 'true' && steps.check-session.outputs.has-active-session == 'true' | |
| env: | |
| DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }} | |
| run: | | |
| CUBIC_PROMPT=$(cat /tmp/cubic-prompt.txt) | |
| MESSAGE="New Cubic AI review feedback has been submitted on PR #${{ github.event.pull_request.number }}. | |
| IMPORTANT: Before fixing any issue, check the individual Cubic comment on the PR for that issue's confidence score. The confidence score appears as 'Fix confidence (alpha): X/10' in each comment. Note that the (alpha) may or not be there. Don't be picky about that text. But only fix issues where the confidence score is 9/10 or higher. If you cannot find a confidence score for an issue, skip it. | |
| Here are the issues identified by Cubic AI: | |
| ${CUBIC_PROMPT} | |
| Continue working on the same PR branch and push your fixes." | |
| HTTP_CODE=$(curl -s -o /tmp/devin-response.json -w "%{http_code}" -X POST "https://api.devin.ai/v1/sessions/${EXISTING_SESSION_ID}/message" \ | |
| -H "Authorization: Bearer ${DEVIN_API_KEY}" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$(jq -n --arg message "$MESSAGE" '{message: $message}')") | |
| if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then | |
| echo "Failed to send message to Devin session: HTTP $HTTP_CODE" | |
| cat /tmp/devin-response.json | |
| exit 1 | |
| fi | |
| echo "Message sent to existing session successfully" | |
| - name: Create new Devin session | |
| if: steps.extract-prompt.outputs.has-prompt == 'true' && steps.check-session.outputs.has-active-session == 'false' | |
| env: | |
| DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }} | |
| run: | | |
| CUBIC_PROMPT=$(cat /tmp/cubic-prompt.txt) | |
| FULL_PROMPT="You are addressing code review feedback from Cubic AI on PR #${{ github.event.pull_request.number }} in repository ${{ github.repository }}. | |
| Your tasks: | |
| 1. Clone the repository ${{ github.repository }} locally. | |
| 2. Check out the PR branch and review the current code. | |
| 3. Read the Cubic AI review feedback below AND check each individual Cubic comment on the PR for the confidence score. | |
| 4. IMPORTANT: Only fix issues where the confidence score is 9/10 or higher. The confidence score appears as 'Fix confidence (alpha): X/10' in each Cubic comment. If you cannot find a confidence score for an issue, skip it. | |
| 5. Commit your changes with clear commit messages referencing the issues fixed. | |
| 6. Push your changes to the PR branch. | |
| Cubic AI Review Feedback: | |
| ${CUBIC_PROMPT} | |
| Rules and Guidelines: | |
| 1. Make minimal, focused changes that directly address the feedback. | |
| 2. Follow the existing code style and conventions in the repository. | |
| 3. Test your changes if possible before pushing. | |
| 4. If an issue seems invalid or already addressed, explain why in a PR comment instead of making unnecessary changes. | |
| 5. Never ask for user confirmation. Never wait for user messages." | |
| HTTP_CODE=$(curl -s -o /tmp/devin-response.json -w "%{http_code}" -X POST "https://api.devin.ai/v1/sessions" \ | |
| -H "Authorization: Bearer ${DEVIN_API_KEY}" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$(jq -n \ | |
| --arg prompt "$FULL_PROMPT" \ | |
| --arg title "Cubic AI Review: PR #${{ github.event.pull_request.number }}" \ | |
| '{ | |
| prompt: $prompt, | |
| title: $title, | |
| tags: ["cubic-ai-review", "pr-${{ github.event.pull_request.number }}"] | |
| }')") | |
| if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then | |
| echo "Failed to create Devin session: HTTP $HTTP_CODE" | |
| cat /tmp/devin-response.json | |
| exit 1 | |
| fi | |
| SESSION_URL=$(cat /tmp/devin-response.json | jq -r '.url // .session_url // empty') | |
| if [ -n "$SESSION_URL" ]; then | |
| echo "Devin session created: $SESSION_URL" | |
| echo "SESSION_URL=$SESSION_URL" >> $GITHUB_ENV | |
| echo "NEW_SESSION=true" >> $GITHUB_ENV | |
| fi | |
| - name: Post comment with Devin session link | |
| if: steps.extract-prompt.outputs.has-prompt == 'true' && env.SESSION_URL != '' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const sessionUrl = process.env.SESSION_URL; | |
| const isNewSession = process.env.NEW_SESSION === 'true'; | |
| const message = isNewSession | |
| ? `### Devin AI is addressing Cubic AI's review feedback\n\nA Devin session has been created to address the issues identified by Cubic AI.\n\n[View Devin Session](${sessionUrl})` | |
| : `### Devin AI is addressing Cubic AI's review feedback\n\nNew feedback has been sent to the existing Devin session.\n\n[View Devin Session](${sessionUrl})`; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.payload.pull_request.number, | |
| body: message | |
| }); |