Skip to content

feat: Add lifecycle hook infrastructure for automated quality gates #18829

feat: Add lifecycle hook infrastructure for automated quality gates

feat: Add lifecycle hook infrastructure for automated quality gates #18829

name: "Velocity Accelerator"
# Detect development acceleration opportunities from repository events
# ADR-006: Thin workflow, business logic in scripts/velocity_accelerator.py
on:
pull_request:
types: [closed]
issues:
types: [opened, labeled]
push:
paths:
- '.agents/**'
workflow_dispatch:
permissions:
contents: read
issues: write
pull-requests: write
concurrency:
group: velocity-accelerator-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.issue.number || github.sha }}
cancel-in-progress: false
env:
GH_TOKEN: ${{ secrets.BOT_PAT }}
jobs:
detect-opportunities:
name: Detect Velocity Opportunities
# ADR-025: ARM runner for cost optimization
runs-on: ubuntu-24.04-arm
timeout-minutes: 5
# Skip bot-generated events
if: >-
github.actor != 'dependabot[bot]' &&
github.actor != 'github-actions[bot]'
outputs:
opportunities: ${{ steps.detect.outputs.opportunities }}
count: ${{ steps.detect.outputs.count }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.14'
- name: Detect opportunities
id: detect
shell: pwsh -NoProfile -Command "& '{0}'"
env:
EVENT_NAME: ${{ github.event_name }}
EVENT_ACTION: ${{ github.event.action }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_MERGED: ${{ github.event.pull_request.merged }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
BEFORE_SHA: ${{ github.event.before }}
AFTER_SHA: ${{ github.sha }}
run: |
$args = @('scripts/velocity_accelerator.py', '--event', $env:EVENT_NAME, '--output-format', 'json')
if ($env:EVENT_ACTION) { $args += @('--action', $env:EVENT_ACTION) }
if ($env:PR_NUMBER -and $env:PR_NUMBER -ne '') { $args += @('--pr-number', $env:PR_NUMBER) }
if ($env:PR_MERGED -eq 'true') { $args += '--pr-merged' }
if ($env:ISSUE_NUMBER -and $env:ISSUE_NUMBER -ne '') { $args += @('--issue-number', $env:ISSUE_NUMBER) }
if ($env:ISSUE_TITLE) { $args += @('--issue-title', $env:ISSUE_TITLE) }
if ($env:ISSUE_BODY) { $args += @('--issue-body', $env:ISSUE_BODY) }
$env:GITHUB_EVENT_BEFORE = $env:BEFORE_SHA
$env:GITHUB_SHA = $env:AFTER_SHA
$output = python @args 2>&1
$exitCode = $LASTEXITCODE
if ($exitCode -eq 2) {
Write-Error "Configuration error running velocity accelerator"
exit 1
}
try {
$opportunities = $output | ConvertFrom-Json
$count = ($opportunities | Measure-Object).Count
} catch {
$opportunities = @()
$count = 0
}
$json = $opportunities | ConvertTo-Json -Compress -Depth 10
if (-not $json -or $json -eq 'null') { $json = '[]' }
"opportunities=$json" >> $env:GITHUB_OUTPUT
"count=$count" >> $env:GITHUB_OUTPUT
Write-Host "Detected $count velocity opportunities"
post-summary:
name: Post Velocity Summary
needs: detect-opportunities
if: needs.detect-opportunities.outputs.count > 0
runs-on: ubuntu-24.04-arm
timeout-minutes: 5
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.14'
- name: Generate and post summary comment
if: github.event_name == 'pull_request' || github.event_name == 'issues'
shell: pwsh -NoProfile -Command "& '{0}'"
env:
OPPORTUNITIES_JSON: ${{ needs.detect-opportunities.outputs.opportunities }}
EVENT_NAME: ${{ github.event_name }}
PR_NUMBER: ${{ github.event.pull_request.number }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
# ADR-006: Format summary in Python script, post via gh CLI
# All user-controlled inputs are in env vars, not interpolated in shell
$tempFile = [System.IO.Path]::GetTempFileName()
$env:OPPORTUNITIES_JSON | Set-Content -Path $tempFile -Encoding utf8
# Generate markdown summary from JSON using Python (keeps logic out of YAML)
$summary = python -c @"
import json, sys
data = json.load(open(sys.argv[1]))
if not data:
sys.exit(0)
lines = ['## Velocity Accelerator: {} Opportunities Detected\n'.format(len(data))]
for opp in data:
lines.append('### {}'.format(opp['title']))
lines.append('- **Type**: `{}`'.format(opp['opportunity_type']))
lines.append('- **Priority**: {}'.format(opp['priority']))
if opp.get('suggested_agent'):
lines.append('- **Suggested Agent**: {}'.format(opp['suggested_agent']))
lines.append('- {}\n'.format(opp['description']))
lines.append('<!-- VELOCITY-ACCELERATOR -->')
print('\n'.join(lines))
"@ $tempFile
Remove-Item $tempFile -ErrorAction SilentlyContinue
if (-not $summary) {
Write-Host "No summary to post"
exit 0
}
if ($env:EVENT_NAME -eq 'pull_request') {
$number = $env:PR_NUMBER
} else {
$number = $env:ISSUE_NUMBER
}
$marker = '<!-- VELOCITY-ACCELERATOR -->'
# Find existing comment with marker using pagination to avoid duplicates
$existingId = $null
$page = 1
$repo = $env:GITHUB_REPOSITORY
do {
$response = gh api "repos/${repo}/issues/${number}/comments?per_page=100&page=${page}" 2>$null
if (-not $response) { break }
$comments = $response | ConvertFrom-Json
if ($comments.Count -eq 0) { break }
foreach ($comment in $comments) {
if ($comment.body -and $comment.body.Contains($marker)) {
$existingId = $comment.id
break
}
}
$page++
} while ($comments.Count -eq 100 -and -not $existingId)
# Write summary to temp file for gh to read (avoids shell escaping issues)
$bodyFile = [System.IO.Path]::GetTempFileName()
$summary | Set-Content -Path $bodyFile -Encoding utf8
$body = Get-Content -Path $bodyFile -Raw
if ($existingId) {
gh api "repos/${repo}/issues/comments/${existingId}" -X PATCH -f body="$body"
Write-Host "Updated existing velocity comment (id: $existingId)"
} else {
gh api "repos/${repo}/issues/${number}/comments" -f body="$body"
Write-Host "Created new velocity comment on #${number}"
}
Remove-Item $bodyFile -ErrorAction SilentlyContinue
- name: Log summary to workflow
env:
OPPORTUNITIES_COUNT: ${{ needs.detect-opportunities.outputs.count }}
run: |
echo "::notice::Velocity Accelerator detected $OPPORTUNITIES_COUNT opportunities"