Skip to content

feat(cli): add ag approve/reject commands for programmatic tool-call … #7433

feat(cli): add ag approve/reject commands for programmatic tool-call …

feat(cli): add ag approve/reject commands for programmatic tool-call … #7433

Workflow file for this run

name: CI
on:
pull_request:
branches:
- main
- develop
push:
branches:
- main
- develop
tags:
- 'v*'
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
lint-pr-title:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: 'feat
fix
docs
style
refactor
perf
test
build
ci
chore
revert
'
requireScope: false
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '22'
cache: npm
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Run hygiene check
run: npm run hygiene-check
- name: 'Security check - prevent shell: true'
run: npm run security-check
- name: Run linter
run: npm run lint
- name: Console.* guard — block new console.log/warn/error in src/
run: bash scripts/console-guard.sh
feat-minor-bump-gate:
if: github.event_name == 'pull_request' && github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- name: Enforce approved minor bump label for feat PRs
uses: actions/github-script@v9
with:
script: |
const pr = context.payload.pull_request;
if (!pr) {
core.info('Not a pull request event; skipping gate.');
return;
}
// Fetch live PR data from the API instead of using the frozen payload.
// This ensures re-runs pick up title changes and labels added after the initial push.
const { data: livePr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
});
const title = livePr.title || '';
const isFeat = /^feat(\(.+\))?!?:\s/i.test(title);
if (!isFeat) {
core.info(`PR title is not feat:* (${title}); gate passed.`);
return;
}
const labels = (livePr.labels || []).map(label => label.name);
if (labels.includes('approved-minor-bump')) {
core.info('approved-minor-bump label present; feat gate passed.');
return;
}
core.setFailed('feat: PRs require the approved-minor-bump label before CI can pass.');
sdk-drift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dorny/paths-filter@v4
id: filter
with:
filters: |
sdk-related:
- 'openapi.yaml'
- 'src/routes/**'
- 'src/schemas/**'
- 'packages/client/**'
- 'packages/python-client/**'
- '.github/workflows/ci.yml'
- uses: actions/setup-node@v6
if: github.event_name == 'push' || steps.filter.outputs.sdk-related == 'true'
with:
node-version: '22'
cache: npm
- uses: actions/setup-python@v6
if: github.event_name == 'push' || steps.filter.outputs.sdk-related == 'true'
with:
python-version: '3.12'
cache: pip
cache-dependency-path: packages/python-client/pyproject.toml
- name: Install dependencies
if: github.event_name == 'push' || steps.filter.outputs.sdk-related == 'true'
run: npm ci
- name: Check generated OpenAPI contract
if: github.event_name == 'push' || steps.filter.outputs.sdk-related == 'true'
run: npm run openapi:check
- name: Check generated TypeScript SDK
if: github.event_name == 'push' || steps.filter.outputs.sdk-related == 'true'
run: npm run sdk:ts:check
- name: Check generated Python SDK
if: github.event_name == 'push' || steps.filter.outputs.sdk-related == 'true'
run: npm run sdk:py:check
test:
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
node-version:
- '20'
- '22'
steps:
- uses: actions/checkout@v6
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Install dependencies
run: npm ci
- name: Supply chain security
run: npm run audit-check
- name: TypeScript check
run: npx tsc --noEmit
- name: Build
run: npm run build
- name: Run server smoke UAT
run: npm run test:smoke
- name: Start Aegis
shell: bash
run: |
node dist/cli.js --port 9100 > aegis.log 2>&1 &
echo "AEGIS_PID=$!" >> "$GITHUB_ENV"
- name: Smoke test health endpoint
run: node scripts/ci-smoke-health.mjs
- name: Stop Aegis
if: always()
shell: bash
run: |
if [ -n "${AEGIS_PID:-}" ]; then
kill "$AEGIS_PID" || true
fi
- name: Check bundle size
run: |
THRESHOLD_KB=2100
SERVER_SIZE=$(find dist/ -name "*.js" ! -path "*/__tests__/*" ! -path "*/dashboard/*" -exec wc -c {} + | tail -1 | awk '{printf "%.0f", $1/1024}')
SERVER_SIZE_KB=$((SERVER_SIZE))
echo "## Bundle Size Report" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "| Scope | Size (KB) | Threshold (KB) | Status |" >> "$GITHUB_STEP_SUMMARY"
echo "|-------|-----------|----------------|--------|" >> "$GITHUB_STEP_SUMMARY"
if [ "$SERVER_SIZE_KB" -gt "$THRESHOLD_KB" ]; then
echo "| Server (excl. tests) | ${SERVER_SIZE_KB} | ${THRESHOLD_KB} | ❌ Exceeds threshold |" >> "$GITHUB_STEP_SUMMARY"
echo "::error::Server bundle size ${SERVER_SIZE_KB}KB exceeds ${THRESHOLD_KB}KB threshold"
exit 1
else
echo "| Server (excl. tests) | ${SERVER_SIZE_KB} | ${THRESHOLD_KB} | ✅ Within budget |" >> "$GITHUB_STEP_SUMMARY"
echo "Server bundle size: ${SERVER_SIZE_KB}KB (threshold: ${THRESHOLD_KB}KB)"
fi
- name: Build dashboard
run: cd dashboard && npm ci && npm run build
- name: Run dashboard E2E tests (PR gate)
if: matrix.os == 'ubuntu-latest' && matrix.node-version == '20'
run: cd dashboard && npx vitest run
- name: Run tests with coverage
run: npm test -- --coverage
- name: Enforce coverage thresholds
if: matrix.os == 'ubuntu-latest' && matrix.node-version == '20'
run: |
echo "## Coverage Threshold Enforcement" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "| Metric | Threshold | Enforced |" >> "$GITHUB_STEP_SUMMARY"
echo "|--------|-----------|----------|" >> "$GITHUB_STEP_SUMMARY"
echo "| Lines | 65% | ✅ |" >> "$GITHUB_STEP_SUMMARY"
echo "| Branches | 65% | ✅ |" >> "$GITHUB_STEP_SUMMARY"
echo "| Functions | 65% | ✅ |" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "> Thresholds are enforced by vitest config (vitest.config.ts)." >> "$GITHUB_STEP_SUMMARY"
echo "> If this step runs, all coverage thresholds passed." >> "$GITHUB_STEP_SUMMARY"
- name: Upload coverage reports
uses: actions/upload-artifact@v4
if: matrix.os == 'ubuntu-latest' && matrix.node-version == '20'
with:
name: coverage-report
path: coverage/
retention-days: 7
- name: Audit tracked files
run: "FOUND=$(git ls-files | grep -E '(^should$|results\\.tsv|^state/|\\.tsbuildinfo$|\\\
.env$|\\.log$|\\.tmp$|\\.bak$|\\.swp$)' || true)\nif [ -n \"$FOUND\" ]; then\n\
\ echo \"::error::Blacklisted files found in git tracking\"\n echo \"$FOUND\"\
\n exit 1\nfi\necho \"Audit passed - no blacklisted files found\"\n"
platform-smoke:
if: ${{ ((github.event_name == 'pull_request' && github.base_ref == 'develop') && github.event.pull_request.draft == false) || (github.event_name == 'push' && github.ref == 'refs/heads/develop') }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- windows-latest
- macos-latest
node-version:
- '22'
steps:
- uses: actions/checkout@v6
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Run server smoke UAT
run: npm run test:smoke
- name: Run platform smoke subset
run: >
npx vitest run
src/__tests__/platform-shell.test.ts
src/__tests__/path-utils-909.test.ts
src/__tests__/hook-paths-909.test.ts
src/__tests__/startup.test.ts
src/__tests__/config.test.ts
- name: Run config watcher smoke
run: npx vitest run src/__tests__/config-hot-reload-1753.test.ts
test-matrix:
if: ${{ startsWith(github.ref, 'refs/tags/') }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
- macos-latest
node-version:
- '20'
- '22'
steps:
- uses: actions/checkout@v6
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Install dependencies
run: npm ci
- name: Supply chain security
run: npm run audit-check
- name: TypeScript check
run: npx tsc --noEmit
- name: Build
run: npm run build
- name: Run server smoke UAT
run: npm run test:smoke
- name: Start Aegis (Linux/macOS)
if: runner.os != 'Windows'
shell: bash
run: |
node dist/cli.js --port 9100 > aegis.log 2>&1 &
echo "AEGIS_PID=$!" >> "$GITHUB_ENV"
- name: Start Aegis (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$proc = Start-Process -FilePath node -ArgumentList 'dist/cli.js', '--port', '9100' -PassThru -WindowStyle Hidden
"AEGIS_PID=$($proc.Id)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: Smoke test health endpoint (Linux/macOS)
if: runner.os != 'Windows'
shell: bash
run: node scripts/ci-smoke-health.mjs
- name: Smoke test health endpoint (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: node scripts/ci-smoke-health.mjs
- name: Stop Aegis (Linux/macOS)
if: always() && runner.os != 'Windows'
shell: bash
run: |
if [ -n "${AEGIS_PID:-}" ]; then
kill "$AEGIS_PID" || true
fi
- name: Stop Aegis (Windows)
if: always() && runner.os == 'Windows'
shell: pwsh
run: |
if ($env:AEGIS_PID) {
Stop-Process -Id ([int]$env:AEGIS_PID) -Force -ErrorAction SilentlyContinue
}
- name: Check bundle size
if: runner.os != 'Windows'
run: |
THRESHOLD_KB=2100
SERVER_SIZE=$(find dist/ -name "*.js" ! -path "*/__tests__/*" ! -path "*/dashboard/*" -exec wc -c {} + | tail -1 | awk '{printf "%.0f", $1/1024}')
SERVER_SIZE_KB=$((SERVER_SIZE))
echo "## Bundle Size Report" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "| Scope | Size (KB) | Threshold (KB) | Status |" >> "$GITHUB_STEP_SUMMARY"
echo "|-------|-----------|----------------|--------|" >> "$GITHUB_STEP_SUMMARY"
if [ "$SERVER_SIZE_KB" -gt "$THRESHOLD_KB" ]; then
echo "| Server (excl. tests) | ${SERVER_SIZE_KB} | ${THRESHOLD_KB} | ❌ Exceeds threshold |" >> "$GITHUB_STEP_SUMMARY"
echo "::error::Server bundle size ${SERVER_SIZE_KB}KB exceeds ${THRESHOLD_KB}KB threshold"
exit 1
else
echo "| Server (excl. tests) | ${SERVER_SIZE_KB} | ${THRESHOLD_KB} | ✅ Within budget |" >> "$GITHUB_STEP_SUMMARY"
echo "Server bundle size: ${SERVER_SIZE_KB}KB (threshold: ${THRESHOLD_KB}KB)"
fi
- name: Build dashboard
run: cd dashboard && npm ci && npm run build
- name: Run dashboard E2E tests (PR gate)
if: matrix.os == 'ubuntu-latest' && matrix.node-version == '20'
run: cd dashboard && npx vitest run
- name: Run tests with coverage
run: npm test -- --coverage
- name: Upload coverage reports
uses: actions/upload-artifact@v4
if: matrix.os == 'ubuntu-latest' && matrix.node-version == '20'
with:
name: coverage-report
path: coverage/
retention-days: 7
- name: Audit tracked files
if: runner.os != 'Windows'
run: "FOUND=$(git ls-files | grep -E '(^should$|results\\.tsv|^state/|\\.tsbuildinfo$|\\\
.env$|\\.log$|\\.tmp$|\\.bak$|\\.swp$)' || true)\nif [ -n \"$FOUND\" ]; then\n\
\ echo \"::error::Blacklisted files found in git tracking\"\n echo \"$FOUND\"\
\n exit 1\nfi\necho \"Audit passed - no blacklisted files found\"\n"
dashboard-e2e:
if: github.event_name == 'pull_request' && github.base_ref == 'develop' && github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
cache: npm
- name: Install dashboard dependencies
run: cd dashboard && npm ci
- name: Install Playwright Chromium
run: cd dashboard && npx playwright install --with-deps chromium
- name: Run dashboard E2E (required specs)
run: cd dashboard && npx playwright test e2e/login.spec.ts e2e/session-list.spec.ts e2e/audit.spec.ts --project=chromium
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v4
with:
name: dashboard-playwright-report
path: dashboard/playwright-report/
retention-days: 7
- name: Upload Playwright test results
if: always()
uses: actions/upload-artifact@v4
with:
name: dashboard-playwright-test-results
path: dashboard/test-results/
retention-days: 7
auto-label-test:
runs-on: ubuntu-latest
if: "github.event_name == 'push' ||\n(github.event_name == 'pull_request' && (\n\
\ contains(github.event.pull_request.changed_files, '.github/actions/auto-label/')\
\ ||\n contains(github.event.pull_request.changed_files, '.github/workflows/auto-label.yml')\n\
))\n"
steps:
- uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Install action dependencies
working-directory: .github/actions/auto-label
run: npm ci
- name: Run auto-label tests
working-directory: .github/actions/auto-label
run: npx vitest run --reporter=verbose