Skip to content

chore(deps-dev): bump the lint-format group across 1 directory with 3 updates #661

chore(deps-dev): bump the lint-format group across 1 directory with 3 updates

chore(deps-dev): bump the lint-format group across 1 directory with 3 updates #661

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
# Cancel in-progress CI runs when a new commit is pushed to the same
# PR / branch. Without this, force-pushes pile up overlapping runs
# that all consume runner minutes pointlessly.
# `cancel-in-progress: true` on PR-triggered runs keeps the queue
# clean; we leave main-branch runs uncancelled so post-merge runs
# always complete (they're rare and the result is load-bearing for
# release dashboards).
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs:
npm-package:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22.x
cache: npm
cache-dependency-path: package-lock.json
- name: Install dependencies
run: npm ci
- name: Verify lockfile is in sync
run: |
# npm ci already fails on package.json/lockfile mismatch, but this
# catches subtler drift: e.g. someone ran `npm install` which silently
# updated resolved URLs or integrity hashes without changing ranges.
npm install --package-lock-only --ignore-scripts
git diff --exit-code package-lock.json || {
echo "::error::package-lock.json is out of sync with package.json. Run 'npm install' locally and commit the updated lockfile."
exit 1
}
- name: Verify npm package wrapper
run: npm test
# ── Go (analysis engine) ────────────────────────────────────
# Matrix-tested across the three OS families we ship binaries for.
# ubuntu remains the canonical runner (race detector + extended smoke
# suite); macos and windows run a reduced "does it build and pass unit
# tests" check. The multi-OS matrix surfaces windows path-separator and
# EOL regressions in PRs rather than after binaries ship.
go-test:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
extended: true
- os: macos-latest
extended: false
- os: windows-latest
extended: false
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0 # full history for fixture repos with git commits
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
- name: Verify go.mod is tidy
if: matrix.extended
run: |
go mod tidy
git diff --exit-code go.mod go.sum
- name: Verify generated docs are up to date
if: matrix.extended
# Hard-fail gate: if you edit internal/signals/manifest.go, run
# `make docs-gen` and commit docs/signals/manifest.json.
run: make docs-verify
- name: Run core Go verification
if: matrix.extended
run: make go-release-verify
- name: Determinism gate
if: matrix.extended
# Three back-to-back runs against the same fixture must produce
# byte-identical snapshots; this gates determinism in CI rather than
# relying on a single-shot unit test. Failure here means a
# non-deterministic data path crept in (typically an unsorted map or
# a timestamp).
run: make test-determinism
- name: Run Go tests
# The race detector adds significant runtime cost; on macos and
# windows runners we omit it to keep PR feedback fast. ubuntu
# remains the canonical environment for race-detection.
run: |
if [ "${{ matrix.extended }}" = "true" ]; then
go test ./internal/... ./cmd/... -count=1 -race
else
go test ./internal/... ./cmd/... -count=1
fi
shell: bash
- name: Smoke-test terrain CLI
if: matrix.extended
run: |
go build -o terrain ./cmd/terrain
./terrain version
./terrain version --json > /dev/null
./terrain --help > /dev/null
./terrain ai --help > /dev/null
./terrain migration --help > /dev/null
./terrain debug --help > /dev/null
./terrain export --help > /dev/null
./terrain analyze --root tests/fixtures/sample-repo --json > /dev/null
./terrain insights --root tests/fixtures/sample-repo --json > /dev/null
- name: Smoke-test fixture matrix
if: matrix.extended
run: |
for fixture in high-fanout weak-coverage legacy-mixed ai-eval-suite skipped-tests; do
echo "--- fixture: $fixture ---"
./terrain analyze --root "tests/fixtures/$fixture" --json > /dev/null
done
- name: Benchmark smoke tests (4 canonical commands)
if: matrix.extended
run: |
echo "=== benchmark: sample-repo ==="
go build -o terrain ./cmd/terrain
./terrain analyze --root tests/fixtures/sample-repo --json | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['testsDetected']['testFileCount'] > 0, 'analyze: no test files'"
./terrain insights --root tests/fixtures/sample-repo --json | python3 -c "import sys,json; d=json.load(sys.stdin); assert 'healthGrade' in d, 'insights: no health grade'"
echo "=== benchmark: ai-eval-suite ==="
./terrain analyze --root tests/fixtures/ai-eval-suite --json | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['testsDetected']['testFileCount'] > 0, 'analyze: no test files'"
./terrain ai list --root tests/fixtures/ai-eval-suite --json | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['summary']['scenarios'] > 0, 'ai list: no scenarios'"
echo "=== benchmark: backend-api ==="
./terrain analyze --root tests/fixtures/backend-api --json | python3 -c "import sys,json; d=json.load(sys.stdin); assert len(d.get('riskPosture',[])) == 5, 'analyze: expected 5 posture dimensions'"
echo "All benchmark smoke tests passed."
extension:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22.x
cache: npm
cache-dependency-path: extension/vscode/package-lock.json
- name: Verify VS Code extension
run: make extension-verify
# ── Go benchmark comparison (PRs only) ──────────────────────
go-bench-compare:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
- name: Install benchstat
run: GOTOOLCHAIN=auto go install golang.org/x/perf/cmd/benchstat@latest
- name: Benchmark current HEAD
run: |
go test ./internal/engine ./internal/analysis ./internal/scoring ./internal/testcase \
-run '^$' \
-bench 'BenchmarkRunPipeline|BenchmarkSignalDetection|BenchmarkBuildImportGraph|BenchmarkRiskScore|BenchmarkExtractTestCases' \
-count=5 > /tmp/bench_head.txt
- name: Benchmark PR base
run: |
git checkout "${{ github.event.pull_request.base.sha }}"
go test ./internal/engine ./internal/analysis ./internal/scoring ./internal/testcase \
-run '^$' \
-bench 'BenchmarkRunPipeline|BenchmarkSignalDetection|BenchmarkBuildImportGraph|BenchmarkRiskScore|BenchmarkExtractTestCases' \
-count=5 > /tmp/bench_base.txt
- name: Compare benchmark results (base vs head)
run: |
git checkout "${{ github.sha }}"
"$(go env GOPATH)/bin/benchstat" /tmp/bench_base.txt /tmp/bench_head.txt | tee /tmp/benchstat.txt
- name: Regression gate (>10% fails the job)
# The gate parses the same benchmark output benchstat consumed,
# computes per-bench delta, and fails if any benchmark regressed
# beyond the threshold.
run: |
go run ./cmd/internal/terrain-bench-gate \
--base /tmp/bench_base.txt \
--head /tmp/bench_head.txt \
--threshold 10 | tee /tmp/bench-gate.txt
- name: Append summary
if: always()
run: |
{
echo '### Go Benchmark Comparison (base vs head)'
echo ''
echo '```'
cat /tmp/benchstat.txt
echo '```'
echo ''
echo '### Regression gate (>10% threshold)'
echo ''
echo '```'
cat /tmp/bench-gate.txt 2>/dev/null || echo "(gate did not run)"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
# ── Docs and CLI surface audit ──────────────────────────────
# Consistency gate between the shipped CLI surface, the docs, and the
# tracked tree: dead links, placeholder copy, --help coverage, tracked
# binaries, changelog discipline, clean discovery output.
docs-and-surface-audit:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0 # tags needed for the changelog discipline check
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
- name: Run docs and surface audit
run: bash scripts/skeptic-audit.sh
# ── Offline guarantee ───────────────────────────────────────
# The docs claim the analyze / gate / fix path needs no API key and
# no network. This job enforces the claim: build the binary, then run
# the core commands inside a network namespace with no interfaces —
# no routes, no DNS, loopback down — against a fixture with a seeded
# prompt↔schema drift, so each command demonstrably does real work
# rather than short-circuiting. Any attempted egress fails the
# command and the job. `terrain serve` is deliberately not covered
# here: it binds loopback by design.
offline-guarantee:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
- name: Build terrain
run: go build -o "$RUNNER_TEMP/terrain" ./cmd/terrain
- name: Verify the namespace actually blocks egress
# Negative control: a green offline run proves nothing if the
# namespace quietly has network access. Guard commands first so
# a broken sudo/unshare/curl can't masquerade as "blocked".
run: |
sudo unshare -n -- true
command -v curl > /dev/null
if sudo unshare -n -- curl --max-time 5 -sS -o /dev/null https://example.com/; then
echo "::error::network namespace is not isolated — egress succeeded"
exit 1
fi
echo "egress blocked as expected"
- name: Run discover / analyze / gate / fix with zero network access
run: |
work="$RUNNER_TEMP/offline"
mkdir -p "$work"
cp tests/fixtures/offline/*.py "$work/"
sudo unshare -n -- bash tests/fixtures/offline/check.sh \
"$RUNNER_TEMP/terrain" "$work" "$PWD/tests/fixtures/offline"