Skip to content

feat: rtl support, body interaction reliability (phase 2) #1142

feat: rtl support, body interaction reliability (phase 2)

feat: rtl support, body interaction reliability (phase 2) #1142

Workflow file for this run

name: AI Risk Assessment
# Runs AFTER risk-label.yml applies the file-path label.
# Only triggers for critical/sensitive PRs — low-risk PRs skip AI entirely.
# Fork PRs skip AI (no access to ANTHROPIC_API_KEY) — file-path label still applies.
#
# - Updates the risk label if AI disagrees with file-path classification
# - Logs full details to workflow output (visible in Actions tab)
on:
pull_request:
types: [opened]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to assess'
required: true
type: number
permissions:
contents: read
pull-requests: write
concurrency:
group: risk-assess-${{ github.event.pull_request.number || github.event.inputs.pr_number }}
cancel-in-progress: true
env:
PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }}
jobs:
assess:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
# Layer 1: file-path classification (same as risk-label.yml)
- name: Classify by file paths
id: filepath
run: |
gh pr diff ${{ env.PR_NUMBER }} --name-only \
| node .github/scripts/risk-label.mjs > /tmp/risk.json
LEVEL=$(node -e "console.log(JSON.parse(require('fs').readFileSync('/tmp/risk.json','utf-8')).level)")
echo "level=$LEVEL" >> $GITHUB_OUTPUT
echo "File-path risk: $LEVEL"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Skip AI when low-risk or API key unavailable (e.g. fork PRs)
- name: Check AI eligibility
id: ai
run: |
if [ "${{ steps.filepath.outputs.level }}" = "low" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
echo "Skipping AI — low risk"
elif [ -z "${{ secrets.ANTHROPIC_API_KEY }}" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
echo "Skipping AI — no API key (fork PR)"
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
# Install Agent SDK (only when AI will run)
- name: Setup Node and Agent SDK
if: steps.ai.outputs.skip != 'true'
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Agent SDK
if: steps.ai.outputs.skip != 'true'
run: npm install --prefix .github/scripts @anthropic-ai/claude-agent-sdk
env:
NODE_ENV: production
# Layer 2+3: AI assessment
- name: Run tiered AI assessment
if: steps.ai.outputs.skip != 'true'
id: assess
run: node .github/scripts/risk-assess.mjs ${{ env.PR_NUMBER }}
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_OUTPUT: $GITHUB_OUTPUT
REPO: ${{ github.repository }}
REPO_ROOT: ${{ github.workspace }}
# Update risk label if AI disagrees with file-path classification
- name: Update risk label from AI
if: steps.ai.outputs.skip != 'true'
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const fs = require('fs');
const results = JSON.parse(fs.readFileSync('/tmp/tiered-risk-assessment.json', 'utf-8'));
const r = results[0];
if (!r || r.error) return;
const aiLevel = r.finalLevel;
const filePathLevel = r.filePath?.level;
const prNumber = ${{ env.PR_NUMBER }};
// Only update if AI assessment differs from file-path
if (aiLevel === filePathLevel) {
console.log(`AI agrees with file-path: ${aiLevel} — no label change`);
return;
}
console.log(`AI reclassified: ${filePathLevel} → ${aiLevel}`);
const LABELS = {
critical: 'review: thorough',
sensitive: 'review: careful',
low: 'review: quick',
};
const owner = context.repo.owner;
const repo = context.repo.repo;
// Remove old risk labels
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
owner, repo, issue_number: prNumber,
});
for (const label of currentLabels.filter(l => l.name.startsWith('review: '))) {
await github.rest.issues.removeLabel({
owner, repo, issue_number: prNumber, name: label.name,
});
}
// Add new label
await github.rest.issues.addLabels({
owner, repo, issue_number: prNumber, labels: [LABELS[aiLevel]],
});