Skip to content

perf(bench): exclude resolution fixtures from incremental-benchmark sweep #3846

perf(bench): exclude resolution fixtures from incremental-benchmark sweep

perf(bench): exclude resolution fixtures from incremental-benchmark sweep #3846

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
name: Lint
steps:
- uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
- name: Install dependencies
timeout-minutes: 20
shell: bash
run: |
for attempt in 1 2 3; do
npm install && break
if [ "$attempt" -lt 3 ]; then
echo "::warning::npm install attempt $attempt failed, retrying in 15s..."
sleep 15
else
echo "::error::npm install failed after 3 attempts"
exit 1
fi
done
- name: Run Biome
run: npx @biomejs/biome check src/ tests/
native-host-build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
name: Native host build (${{ matrix.os }})
steps:
- uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: crates/codegraph-core
- name: Install napi-rs CLI
timeout-minutes: 5
run: npm install -g @napi-rs/cli@3
- name: Build native addon
working-directory: crates/codegraph-core
run: napi build --release
# Runs `cargo test`, which exercises the grammar-ABI regression test
# added in #1054. Without this step a future tree-sitter / grammar
# version drift would only surface as a runtime "files dropped"
# warning during benchmarks, not as a test failure on the PR.
- name: Run Rust tests
working-directory: crates/codegraph-core
run: cargo test --release
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: native-host-${{ matrix.os }}
path: crates/codegraph-core/*.node
if-no-files-found: error
test:
needs: native-host-build
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: [22]
runs-on: ${{ matrix.os }}
name: Test Node ${{ matrix.node-version }} (${{ matrix.os }})
steps:
- uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
timeout-minutes: 20
shell: bash
run: |
for attempt in 1 2 3; do
npm install && break
if [ "$attempt" -lt 3 ]; then
echo "::warning::npm install attempt $attempt failed, retrying in 15s..."
sleep 15
else
echo "::error::npm install failed after 3 attempts"
exit 1
fi
done
- name: Download PR-built native addon
uses: actions/download-artifact@v8
with:
name: native-host-${{ matrix.os }}
path: crates/codegraph-core
- name: Install native addon over published binary
shell: bash
run: node scripts/ci-install-native.mjs
- name: Run tests
run: npm test
typecheck:
runs-on: ubuntu-latest
name: TypeScript type check
steps:
- uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
- name: Install dependencies
timeout-minutes: 20
shell: bash
run: |
for attempt in 1 2 3; do
npm install && break
if [ "$attempt" -lt 3 ]; then
echo "::warning::npm install attempt $attempt failed, retrying in 15s..."
sleep 15
else
echo "::error::npm install failed after 3 attempts"
exit 1
fi
done
- name: Type check
run: npm run typecheck
audit:
runs-on: ubuntu-latest
name: Security audit
steps:
- uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
- name: Install dependencies
timeout-minutes: 20
run: npm ci
- name: Audit production dependencies
run: npm audit --omit=dev --audit-level=high
verify-imports:
runs-on: ubuntu-latest
name: Verify dynamic imports
steps:
- uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
- name: Verify all dynamic imports resolve
run: |
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
node $STRIP_FLAG scripts/verify-imports.ts
parity:
needs: native-host-build
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
name: Engine parity (${{ matrix.os }})
steps:
- uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
- name: Install dependencies
timeout-minutes: 20
shell: bash
run: |
for attempt in 1 2 3; do
npm install && break
if [ "$attempt" -lt 3 ]; then
echo "::warning::npm install attempt $attempt failed, retrying in 15s..."
sleep 15
else
echo "::error::npm install failed after 3 attempts"
exit 1
fi
done
- name: Download PR-built native addon
uses: actions/download-artifact@v8
with:
name: native-host-${{ matrix.os }}
path: crates/codegraph-core
- name: Install native addon over published binary
shell: bash
run: node scripts/ci-install-native.mjs
- name: Verify native addon is available
shell: bash
run: |
node -e "
const { createRequire } = require('node:module');
const r = createRequire(require.resolve('./package.json'));
const os = require('os');
const fs = require('fs');
const plat = os.platform();
const arch = os.arch();
let libc = '';
if (plat === 'linux') {
try {
const files = fs.readdirSync('/lib');
libc = files.some(f => f.startsWith('ld-musl-') && f.endsWith('.so.1')) ? 'musl' : 'gnu';
} catch { libc = 'gnu'; }
}
const pkgs = {
'linux-x64-gnu': '@optave/codegraph-linux-x64-gnu',
'linux-x64-musl': '@optave/codegraph-linux-x64-musl',
'linux-arm64-gnu': '@optave/codegraph-linux-arm64-gnu',
'linux-arm64-musl': '@optave/codegraph-linux-arm64-musl',
'darwin-arm64': '@optave/codegraph-darwin-arm64',
'darwin-x64': '@optave/codegraph-darwin-x64',
'win32-x64': '@optave/codegraph-win32-x64-msvc',
};
const key = libc ? plat + '-' + arch + '-' + libc : plat + '-' + arch;
const pkg = pkgs[key];
if (!pkg) { console.error('No native package for ' + key); process.exit(1); }
try { r(pkg); console.log('Native addon loaded: ' + pkg); }
catch (e) { console.error('Failed to load ' + pkg + ': ' + e.message); process.exit(1); }
"
- name: Run parity tests
shell: bash
env:
CODEGRAPH_PARITY: '1'
run: npx vitest run tests/engines/ tests/integration/build-parity.test.ts --reporter=verbose
rust-check:
runs-on: ubuntu-latest
name: Rust compile check
steps:
- uses: actions/checkout@v6
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: crates/codegraph-core
- name: Check compilation
run: cargo check --workspace
# ── Pre-publish benchmark gate ──
#
# Mirrors the gate in publish.yml so every PR catches regressions before
# merge instead of at release time. Measures the PR-built native artifact
# against the local source as version "dev", appends to the benchmark
# history files (in-job only — never committed from CI), and runs the
# regression guard against the most recent release baseline.
#
# Long-running but parallel with the rest of CI, so it does not extend
# the critical path for fast-failing checks (lint/typecheck/test).
pre-publish-benchmark:
name: Pre-publish benchmark gate
# Only run on PRs — push-to-main re-runs the same benchmarks the merged
# PR already gated on, doubling CI minutes per landed change with no new
# signal. Mirrors the `if: github.event_name != 'push'` skip in
# publish.yml's equivalent gate.
if: github.event_name == 'pull_request'
needs: native-host-build
runs-on: ubuntu-latest
env:
# Surface why detectNoChanges returns false on each fast-skip pre-flight
# so we can pinpoint the cause of the ~2s incremental-rebuild regression
# observed in CI but not locally (#1066). Remove once root cause is fixed.
CODEGRAPH_FAST_SKIP_DIAG: "1"
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: "npm"
- name: Setup Python (for resolution benchmark + tracer validation)
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Setup Go (for resolution benchmark + tracer validation)
uses: actions/setup-go@v6
with:
go-version: "stable"
cache: false
- name: Download PR-built native addon
uses: actions/download-artifact@v8
with:
name: native-host-ubuntu-latest
path: crates/codegraph-core/
- name: Install dependencies
timeout-minutes: 20
shell: bash
run: |
for attempt in 1 2 3; do
npm install && break
if [ "$attempt" -lt 3 ]; then
echo "::warning::npm install attempt $attempt failed, retrying in 15s..."
sleep 15
else
echo "::error::npm install failed after 3 attempts"
exit 1
fi
done
- name: Install native addon over published binary
run: node scripts/ci-install-native.mjs
# Build dist/ so benchmarks load the same compiled JS that ships to npm.
# Historical baselines (v3.9.6 and earlier) were measured against dist/
# via the post-publish --npm path; running against src/ with --strip-types
# changes the load path and introduces version-to-version noise unrelated
# to the code under test (#1055).
- name: Build TypeScript
run: npm run build
- name: Run build benchmark
timeout-minutes: 20
run: |
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
node $STRIP_FLAG --import ./scripts/ts-resolve-loader.js scripts/benchmark.ts --version dev --dist > benchmark-result.json
- name: Run resolution benchmark
timeout-minutes: 20
run: |
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
node $STRIP_FLAG --import ./scripts/ts-resolve-loader.js scripts/resolution-benchmark.ts --version dev --dist > resolution-result.json
- name: Gate on resolution thresholds
timeout-minutes: 30
run: npx vitest run tests/benchmarks/resolution/resolution-benchmark.test.ts --reporter=verbose
- name: Run tracer validation (same-file edge recall)
timeout-minutes: 10
run: npx vitest run tests/benchmarks/resolution/tracer/tracer-validation.test.ts --reporter=verbose
- name: Merge resolution into build result
run: |
node -e "
const fs = require('fs');
const build = JSON.parse(fs.readFileSync('benchmark-result.json', 'utf8'));
const resolution = JSON.parse(fs.readFileSync('resolution-result.json', 'utf8'));
build.resolution = resolution;
fs.writeFileSync('benchmark-result.json', JSON.stringify(build, null, 2));
"
- name: Run query benchmark
timeout-minutes: 20
run: |
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
node $STRIP_FLAG --import ./scripts/ts-resolve-loader.js scripts/query-benchmark.ts --version dev --dist > query-benchmark-result.json
- name: Run incremental benchmark
timeout-minutes: 20
run: |
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
node $STRIP_FLAG --import ./scripts/ts-resolve-loader.js scripts/incremental-benchmark.ts --version dev --dist > incremental-benchmark-result.json
- name: Update build report
run: |
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
node $STRIP_FLAG scripts/update-benchmark-report.ts benchmark-result.json
- name: Update query report
run: |
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
node $STRIP_FLAG scripts/update-query-report.ts query-benchmark-result.json
- name: Update incremental report
run: |
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
node $STRIP_FLAG scripts/update-incremental-report.ts incremental-benchmark-result.json
- name: Regression guard
env:
RUN_REGRESSION_GUARD: "1"
run: npm run test:regression-guard
# Always upload raw JSON so a failed regression guard is debuggable
# without re-running the full benchmark suite locally.
- name: Upload benchmark JSON results
if: always()
uses: actions/upload-artifact@v7
with:
name: benchmark-results-json
path: |
benchmark-result.json
query-benchmark-result.json
incremental-benchmark-result.json
if-no-files-found: warn
ci-pipeline:
if: always()
needs: [lint, native-host-build, test, typecheck, audit, verify-imports, rust-check, parity, pre-publish-benchmark]
runs-on: ubuntu-latest
name: CI Testing Pipeline
steps:
- name: Check results
run: |
if [[ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
echo "One or more CI jobs failed or were cancelled."
exit 1
fi