mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
76453add71
* refactor: pnpm workspace + @agent-device/kernel pilot (#1490 W0) Extend the workspace with packages/* and move the kernel behind an enforced public API: packages/kernel with nine consumer-earned subpath exports (errors, device, snapshot, contracts, collections, rect, redaction, daemon-error, bounds — the last absorbed from utils as Rect vocabulary). Every kernel import repo-wide becomes the @agent-device/kernel/<sub> specifier; kernel tests move to src/__tests__/kernel/ and exercise the package surface. The root declares the package in devDependencies (workspace:*), tsdown bundles it (noExternal) so the published artifact and its runtime dependency manifest are unchanged. Gate rewiring in the same change, per the W0 brief: - R1 kernel-sink retires (physically subsumed); new R11 package-boundaries guards no-root-back-imports, relative tunnelling past exports maps, undeclared workspace deps, and non-exported subpaths, with runtime resolution pins via import.meta.resolve. - resolveImportEdges and mutation ownership follow workspace specifiers through exports maps, keeping R4 cycle checks, depgraph, and derived test ownership connected across the seam (kernel-errors still owns 495 tests). listSourceFiles includes packages/*/src. - kernel becomes an unranked zone; mutation registry, stryker mutate globs, and the mutation-affected workflow path filter move to packages/kernel/src/errors.ts. - check:affected gains packages/ ownership (manifests fail open); vitest and coverage include packages/*/src; fallow ignores packages/** (its resolver cannot follow workspace specifiers). - The affected-selector CI job installs dependencies: its closure now crosses workspace specifiers, and the R8 relative exception is unsafe for production src files (Node ESM does not realpath, so dual specifier/relative loads would instantiate modules twice). The R8 zero-dep set is pinned empty with that rationale. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep * fix: address W0 review — mutation sandbox, exports-map resolution, tsc -b Review findings on #1494, all five: 1. contracts-schema-public.test.ts reads the kernel source at its packages/ path (fs access invisible to the codemod and typecheck). 2. Mutation lane: Stryker sandboxes the tree but pnpm's node_modules symlink resolves @agent-device/* back to the real repo, so mutants in the sandbox never load and vitest.related finds no tests. vitest.mutation.config.ts now aliases each EXPORTED specifier to its source (derived from exports maps, never a wildcard), keeping resolution inside the mutated tree. Validated: kernel-errors module runs end to end (dry run 3,984 tests, mutants killed, exit 0). 3. Layering/depgraph resolve workspace specifiers through the exports-derived map (workspaceSpecifierTargets) instead of reconstructing paths, so '.'-facade packages resolve; the positional fallback remains only for map-less fixtures (P0 pin). 4. Per-package project references implemented: packages/kernel is composite (emitDeclarationOnly -> dist-types, gitignored), the root references it, and typecheck becomes tsc -b — probed to catch type errors on both sides under TypeScript 7 native. 5. R11's relative-route exception now requires membership in an actual R8 zero-dep job closure (zeroDepClosureFiles walks entries), not mere scripts/ placement — closing the dual-instantiation bypass. Also from review discussion: daemon-error moves out of the kernel package to src/client/ — its consumers (cli, client facade) rehydrate wire DaemonErrors client-side; the daemon only produces them. Kernel drops to 8 exported subpaths before any of them ship. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep * refactor: one exports-map reader for mutation alias and ownership Fallow flagged workspaceExportAliases (cognitive 15, CRAP 90). The manifest-reading logic already exists as workspaceSpecifierTargets in scripts/layering/package-boundaries.ts, so both the Stryker sandbox alias table and the mutation ownership walker now consume it instead of carrying near-clones. Behavior unchanged; mutation suite 45/45 and changed-code fallow green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep * fix: composite kernel without a root references edge FreeRange runs plain `tsc -p tsconfig.json`, and a root `references` entry makes non-build-mode TypeScript demand the referenced project's built declarations (TS6305) — a standing "build first" tax on every plain -p consumer (fr, editors). Keep the per-package composite project and build it in typecheck (`tsc -b packages/kernel` before the root and examples/sdk passes), but drop the root references edge: root consumption resolves through exports to source, identical to runtime and to the bundler. Probed: plain -p green with no prebuilt output; kernel-side type errors still caught by its own build. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep * fix: R11 uses the layering parser; mutation config is a fallow entry Review blockers on #1494: - R11's private single-quote regex could miss a double-quoted or re-export route into packages/*/src. specifierSites now delegates to the layering model's parseImports (both quote styles, side-effect imports, re-exports, dynamic imports), with direct regressions for each formerly-invisible form. - vitest.mutation.config.ts becomes a declared fallow entry instead of a tolerated unused-file finding: the full-repo audit now reports it reachable (unused files 2 -> 1; the remainder predates this PR). FreeRange clean-checkout evidence: with packages/kernel/dist-types and every *.tsbuildinfo deleted, `pnpm check:freerange` reports 0 findings on this head — the TS6305 topology died with the root references edge in the previous commit; check:freerange has no build precondition. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep --------- Co-authored-by: Claude <noreply@anthropic.com>
381 lines
14 KiB
YAML
381 lines
14 KiB
YAML
name: CI
|
|
|
|
on:
|
|
pull_request:
|
|
paths-ignore:
|
|
- 'docs/**'
|
|
- 'website/**'
|
|
- 'README.md'
|
|
- '.github/actions/build-docs/action.yml'
|
|
- '.github/workflows/deploy.yml'
|
|
- '.github/workflows/pr-preview.yml'
|
|
- '.github/workflows/pr-preview-cleanup.yml'
|
|
push:
|
|
branches:
|
|
- main
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# Two single-`rg`-assertion jobs (formerly `ios-runner-swift-compat`,
|
|
# `no-test-di-seams`, added independently in b79bd8601 / 9eb060406) folded
|
|
# into steps here: each was checkout + one grep, paying full job
|
|
# scheduling/checkout overhead and its own PR status-check line for what is
|
|
# a single assertion. Each step keeps its own failure message. See #1462.
|
|
static-checks:
|
|
name: Static Checks
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Disallow trailing commas before closing parenthesis in Swift
|
|
run: |
|
|
if rg -nU --glob '*.swift' ',\s*\n\s*\)' apple/runner; then
|
|
echo "Found trailing commas before ')' in Swift files. This syntax requires Swift 6.1+ and breaks older Xcode toolchains."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Fail if test-only DI seams reappear in production code
|
|
run: |
|
|
if rg '\?\s*:\s*typeof\s+' src/ --glob '!**/__tests__/**' --glob '!*.test.ts'; then
|
|
echo "Found test-only DI seams (optional typeof params) in production code."
|
|
exit 1
|
|
fi
|
|
|
|
swift-runner-unit-compile:
|
|
name: Swift Runner Unit Compile
|
|
runs-on: macos-26
|
|
timeout-minutes: 20
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Setup toolchain
|
|
uses: ./.github/actions/setup-node-pnpm
|
|
|
|
- name: Restore and compile Swift runner unit-test surface
|
|
uses: ./.github/actions/setup-apple-runner-build
|
|
with:
|
|
derived-path: ${{ github.workspace }}/.tmp/swift-runner-unit-derived
|
|
cache-key-prefix: swift-runner-unit
|
|
build-command: AGENT_DEVICE_XCUITEST_INCLUDE_UNIT_TESTS=1 pnpm build:xcuitest:macos
|
|
xcuitest-platform: macos
|
|
xcuitest-destination: platform=macOS,arch=arm64
|
|
|
|
lint:
|
|
name: Lint & Format
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Setup toolchain
|
|
uses: ./.github/actions/setup-node-pnpm
|
|
|
|
- name: Run oxlint
|
|
run: pnpm lint
|
|
|
|
- name: Check formatting
|
|
run: pnpm format:check
|
|
|
|
layering-guard:
|
|
name: Layering Guard
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
# This job used to run with install-deps: false, and R8 still holds every
|
|
# remaining zero-dep job to that contract. The layering guard itself opted out
|
|
# when R7 (SessionState ownership) started parsing the daemon with `oxc-parser`
|
|
# instead of matching assignment operators with a regex: a regex cannot see
|
|
# `??=` or a computed `session[key] =` write, so the choice was a real parser or
|
|
# a rule with holes in it. Keep install-deps enabled.
|
|
- name: Setup toolchain
|
|
uses: ./.github/actions/setup-node-pnpm
|
|
|
|
- name: Check import-direction DAG
|
|
# Generalizes the former inline commands/-import grep into a structured
|
|
# import-direction lint over the resolved graph. See scripts/layering/check.ts
|
|
# and CONTEXT.md (Architecture: folder DAG + layering lint).
|
|
run: pnpm check:layering
|
|
|
|
- name: Check the depgraph report agrees with the gate
|
|
# scripts/depgraph reads the same model as the gate, so its inversion count must
|
|
# reproduce TYPE_INVERSION_BASELINE. Free two-sources check: if the tree changes
|
|
# and only one side is updated, this fails and names the difference. Runs here
|
|
# rather than in its own job so the two can never be green independently.
|
|
run: pnpm depgraph:test
|
|
|
|
affected-selector:
|
|
name: Affected-check Selector
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
# The selector's entry closure reaches `@agent-device/kernel` workspace
|
|
# specifiers through src/utils/exec.ts and diagnostics.ts (#1490 W0), and
|
|
# workspace package resolution needs the pnpm link in node_modules. The
|
|
# R8 relative-import exception is reserved for scripts, not production
|
|
# src files, so this job installs dependencies instead.
|
|
- name: Setup toolchain
|
|
uses: ./.github/actions/setup-node-pnpm
|
|
|
|
# The selector is fail-open and advisory (GitHub CI stays authoritative),
|
|
# so the gate only guards the derivation model.
|
|
- name: Check affected-selector model
|
|
run: pnpm check:affected:test
|
|
|
|
maestro-conformance:
|
|
name: Maestro Conformance Oracle
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
# Unlike the layering/affected guards, this job DOES install deps: the
|
|
# verifier parses corpus flows with the live engine, and the Maestro parser
|
|
# imports the `yaml` package. Keep install-deps enabled.
|
|
- name: Setup toolchain
|
|
uses: ./.github/actions/setup-node-pnpm
|
|
|
|
# Layers 1-2 of the conformance oracle: replay the JVM-generated fixtures
|
|
# against the live engine. Deterministic and Java-free — the generated
|
|
# fixtures are checked in and only regenerated on an upstream-pin bump. The
|
|
# device-backed layer 3 runs on the scheduled conformance-differential
|
|
# workflow. See scripts/maestro-conformance/README.md.
|
|
- name: Verify Maestro conformance fixtures
|
|
run: pnpm maestro:conformance
|
|
|
|
packaged-cli-node-22-12:
|
|
name: Packaged CLI Node 22.12
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Setup build toolchain
|
|
uses: ./.github/actions/setup-node-pnpm
|
|
|
|
- name: Build and pack CLI
|
|
run: |
|
|
pnpm build
|
|
pnpm check:bundle-owner-files
|
|
mkdir -p .tmp/node-compat
|
|
npm pack --ignore-scripts --pack-destination .tmp/node-compat
|
|
|
|
- name: Setup Node.js 22.12
|
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
|
with:
|
|
node-version: '22.12'
|
|
|
|
- name: Install and run global CLI on Node.js 22.12
|
|
run: |
|
|
set -euo pipefail
|
|
prefix="$RUNNER_TEMP/agent-device-node-22-12"
|
|
tarball="$(find "$GITHUB_WORKSPACE/.tmp/node-compat" -name 'agent-device-*.tgz' -print -quit)"
|
|
test -n "$tarball"
|
|
npm install --global --prefix "$prefix" --ignore-scripts "$tarball"
|
|
"$prefix/bin/agent-device" --version
|
|
"$prefix/bin/agent-device" help
|
|
"$prefix/bin/agent-device" doctor --remote --json
|
|
|
|
fallow:
|
|
name: Fallow Code Quality
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Setup toolchain
|
|
uses: ./.github/actions/setup-node-pnpm
|
|
|
|
- name: Run Fallow audit
|
|
env:
|
|
FALLOW_BASE: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
|
|
run: pnpm check:fallow --base "$FALLOW_BASE"
|
|
|
|
- name: Check for production-unused exports
|
|
run: pnpm check:production-exports
|
|
|
|
replay-compat-provenance:
|
|
# The frozen replay-compat corpus (#1417) claims each entry was published by
|
|
# a released tag. Only a full-history checkout can re-derive that claim, so
|
|
# this job exists separately from the shallow-clone-safe unit lane.
|
|
name: Replay-Compat Provenance
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
fetch-depth: 0
|
|
fetch-tags: true
|
|
|
|
- name: Setup toolchain
|
|
uses: ./.github/actions/setup-node-pnpm
|
|
|
|
- name: Verify corpus entries against their released blobs
|
|
run: pnpm check:replay-compat
|
|
|
|
coverage:
|
|
# Runs the full unit + provider-integration suites under coverage with
|
|
# thresholds, so a separate unit-tests job would rerun the same tests.
|
|
name: Coverage
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Setup toolchain
|
|
uses: ./.github/actions/setup-node-pnpm
|
|
|
|
- name: Test changed-line coverage gate
|
|
run: pnpm check:coverage-changed:test
|
|
|
|
# The retry list is an enumerated set of owned waivers, so an expired entry
|
|
# must fail before the suite runs rather than quietly keeping its retry.
|
|
- name: Check contention retry policy
|
|
run: pnpm check:contention-retry
|
|
|
|
# Wrapped in the single-retry policy (#1419): a timeout-shaped failure in
|
|
# an enumerated contention-flaky file reruns that file once and reports it
|
|
# in the job summary. Assertion failures fail here on the first run.
|
|
- name: Run coverage
|
|
env:
|
|
OUTPUT_ECONOMY_BASE: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
|
|
run: pnpm test:coverage:ci
|
|
|
|
- name: Upload contention-retry envelope
|
|
if: always()
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
with:
|
|
name: contention-retry-envelope
|
|
if-no-files-found: ignore
|
|
path: .tmp/contention-retry/lane-envelope.json
|
|
|
|
# Reuses the lcov the coverage step just wrote (never runs coverage twice)
|
|
# and fails when changed-line coverage < the threshold in
|
|
# scripts/coverage-changed/model.ts. The `coverage-waiver` PR label maps to
|
|
# the waiver env, which skips the failure but still prints the numbers.
|
|
- name: Enforce changed-line coverage gate
|
|
if: always() && github.event_name == 'pull_request'
|
|
env:
|
|
AGENT_DEVICE_COVERAGE_WAIVER: ${{ contains(github.event.pull_request.labels.*.name, 'coverage-waiver') }}
|
|
run: pnpm check:coverage-changed --base "${{ github.event.pull_request.base.sha }}"
|
|
|
|
typecheck:
|
|
name: Typecheck
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Setup toolchain
|
|
uses: ./.github/actions/setup-node-pnpm
|
|
|
|
- name: Run typecheck
|
|
run: pnpm typecheck
|
|
|
|
freerange:
|
|
name: FreeRange
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Setup toolchain
|
|
uses: ./.github/actions/setup-node-pnpm
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
|
|
|
- name: Check numeric ranges
|
|
run: pnpm check:freerange
|
|
|
|
integration:
|
|
name: Integration Tests
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Setup toolchain
|
|
uses: ./.github/actions/setup-node-pnpm
|
|
|
|
- name: Run integration tests
|
|
run: |
|
|
pnpm clean:daemon
|
|
pnpm test:integration:node
|
|
|
|
- name: Run seeded concurrency torture lane (fast PR sweep)
|
|
# #1416's nightly torture lane lives under test/integration/nightly/, out
|
|
# of the test:integration:node glob, so this is a *deliberate* fast PR
|
|
# sweep (TORTURE_RUNS default 128 seeds, ~sub-second) — not an accidental
|
|
# glob inclusion. The Concurrency Torture Nightly workflow sweeps a much
|
|
# larger seed range on schedule.
|
|
run: pnpm test:concurrency-torture
|
|
|
|
- name: Run provider-backed integration tests
|
|
run: pnpm test:integration:provider
|
|
|
|
- name: Check Provider-backed integration architecture progress
|
|
run: pnpm test:integration:progress:check
|
|
|
|
# A build-cache lookup outage must degrade setup-fixture-app to an inline
|
|
# build, not fail the caller. This drives that step's real shell against a
|
|
# failing `gh`.
|
|
- name: Setup-fixture-app cache-failure fallback
|
|
run: sh ./test/scripts/setup-fixture-app-fallback-smoke.sh
|
|
|
|
web-smoke:
|
|
name: Web Platform Smoke
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
env:
|
|
AGENT_DEVICE_WEB_E2E: '1'
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Setup toolchain
|
|
uses: ./.github/actions/setup-node-pnpm
|
|
with:
|
|
node-version: '24.13'
|
|
|
|
- name: Run live web smoke
|
|
run: |
|
|
pnpm clean:daemon
|
|
pnpm test:smoke:web
|
|
|
|
- name: Upload web smoke artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
with:
|
|
name: web-smoke-artifacts
|
|
if-no-files-found: ignore
|
|
path: |
|
|
test/artifacts/web/**
|