Skip to content

feat(codex): record plugins-overview parts (MCP UI, browser extensions, scheduled tasks) as dated capability rows (#188) #1170

feat(codex): record plugins-overview parts (MCP UI, browser extensions, scheduled tasks) as dated capability rows (#188)

feat(codex): record plugins-overview parts (MCP UI, browser extensions, scheduled tasks) as dated capability rows (#188) #1170

Workflow file for this run

name: CI
on:
pull_request:
push:
branches: [main]
# Nightly release-boundary matrix (packed-matrix job): the scaffolder
# template tests beyond the per-PR minimal-template smoke.
schedule:
- cron: '17 6 * * *'
workflow_dispatch:
permissions:
contents: read
# PR pushes cancel the superseded run of the same PR (only the latest commit
# matters). Pushes to main, the nightly schedule, and manual dispatches get
# SHA-keyed groups with no cancellation, so a new main push can never kill an
# in-flight main or nightly run. Same pattern as package-preview.yml.
concurrency:
group: ci-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
# PR-only docs allowlist: docs/**, agent-patterns/**, .changeset/*.md, and
# top-level *.md. Nested markdown elsewhere is code because examples and
# packages contain compiled SKILL.md artifacts, and package markdown affects
# npm pack audits. Classification fails open so uncertain PRs run every heavy
# job; pushes to main never skip any job based on changed paths.
# Path rules and fail-open listing checks live in
# scripts/classify-docs-only.mjs (unit-tested). This job sparse-checkouts
# only that script so the vendored Effect subtree never lands on the
# classify critical path.
changes:
if: github.event_name == 'pull_request'
name: Detect changed paths
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
pull-requests: read
outputs:
docs_only: ${{ steps.classify.outputs.docs_only }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 1
filter: blob:none
persist-credentials: false
sparse-checkout: |
scripts/classify-docs-only.mjs
sparse-checkout-cone-mode: false
- name: Classify changed files
id: classify
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
set -euo pipefail
files_file="$(mktemp)"
trap 'rm -f "$files_file"' EXIT
if ! changed_files="$(
gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" \
--jq '.changed_files'
)"; then
echo "Could not read the PR changed-files count; failing open so heavy jobs run."
node scripts/classify-docs-only.mjs --listing-error
exit 0
fi
if ! gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files" \
--paginate \
--jq '.[] | [.filename, (.previous_filename // "")] | @tsv' \
> "$files_file"; then
echo "Could not list changed files; failing open so heavy jobs run."
node scripts/classify-docs-only.mjs --listing-error
exit 0
fi
node scripts/classify-docs-only.mjs \
--changed-files-count "$changed_files" \
--listing "$files_file"
# Builds and checks every public example through its own toolchain.
examples-check:
needs: changes
if: >-
${{ !cancelled() && github.event_name != 'schedule' &&
(github.event_name != 'pull_request' || needs.changes.outputs.docs_only != 'true') }}
name: Examples check (Node 22.19)
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v7
- uses: pnpm/setup@v2
with:
cache: true
install: false
runtime: node@22.19.0
- run: pnpm install --frozen-lockfile
- run: pnpm examples:check
verify:
needs: changes
if: >-
${{ !cancelled() && github.event_name != 'schedule' &&
(github.event_name != 'pull_request' || needs.changes.outputs.docs_only != 'true') }}
name: Verify (Node ${{ matrix.node-version }})
runs-on: ubuntu-latest
timeout-minutes: 45
# Measured PR Verify cost was 3 legs × ~7 minutes, about 75% of PR
# runner-minutes. PRs run newest-LTS Node 24; every main push and manual
# dispatch still gates on the full supported matrix.
strategy:
fail-fast: false
matrix:
node-version: ${{ github.event_name == 'pull_request' && fromJSON('["24"]') || fromJSON('["22.19.0","24","26"]') }}
steps:
- uses: actions/checkout@v7
- uses: pnpm/setup@v2
with:
cache: true
install: false
runtime: node@${{ matrix.node-version }}
- run: pnpm install --frozen-lockfile
# Tests launch branded Chrome (`channel: 'chrome'`), which Playwright
# installs system-wide via apt — never into ~/.cache/ms-playwright, so
# a browser cache cannot help. In CI, `playwright install chrome`
# unconditionally removes and re-downloads ~120 MB of Chrome per job,
# while ubuntu-latest images already ship Google Chrome stable with its
# OS deps. Reuse it; fall back to the full install (browser + deps) if
# the runner image ever drops it.
- name: Ensure branded Chrome for Playwright
run: |
if command -v google-chrome >/dev/null 2>&1; then
echo "Using preinstalled $(google-chrome --version)"
else
pnpm exec playwright install --with-deps chrome
fi
# Build first: checked-in suites and API tests import the package's built type
# declarations, so typecheck requires dist (same order as `pnpm check`).
- run: pnpm build
- name: Package lint (publint)
run: pnpm lint:package
- run: pnpm typecheck
- run: pnpm lint
- run: pnpm test
release-gates:
needs: changes
if: >-
${{ !cancelled() && github.event_name != 'schedule' &&
(github.event_name != 'pull_request' || needs.changes.outputs.docs_only != 'true') }}
name: Release gates (Node 22.19)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- uses: pnpm/setup@v2
with:
cache: true
install: false
runtime: node@22.19.0
- run: pnpm install --frozen-lockfile
# Reuse the runner image's Chrome; see the Verify job for the rationale.
- name: Ensure branded Chrome for Playwright
run: |
if command -v google-chrome >/dev/null 2>&1; then
echo "Using preinstalled $(google-chrome --version)"
else
pnpm exec playwright install --with-deps chrome
fi
# Per-PR packed pool: single pack+install proofs plus the
# minimal-template scaffolder smoke. The full template matrix runs in
# the nightly packed-matrix job and in pre-publish `pnpm check:release`.
- run: pnpm check:release:ci
# Binary-gated real-host install proofs. The proof suites skip their Claude
# and Codex legs when the CLIs are absent, which is how #364 changed the
# Codex `interface.logo` emission and broke both proofs on main without CI
# noticing (#367/#368 repaired them from local runs). This job installs the
# exact CLI versions pinned in each adapter's schema PROVENANCE.json
# (`hostCli`, kept equal to `observedCliVersion` by scripts/host-cli-pins.mjs),
# fails closed if `claude`/`codex --version` differs from the pin, and runs
# the source-built proofs, the packed-tarball proofs, and the packed Claude
# plugin validation. Nothing here needs a login: every proof runs against an
# isolated HOME / CLAUDE_CONFIG_DIR / CODEX_HOME and no secrets are passed.
# Signed-in smokes (`claude -p`, `codex exec`, the Eval harnesses) stay in
# the opt-in native-host-smoke workflow.
host-install-proofs:
needs: changes
if: >-
${{ !cancelled() && github.event_name != 'schedule' &&
(github.event_name != 'pull_request' || needs.changes.outputs.docs_only != 'true') }}
name: Host install proofs (Node 22.19)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- uses: pnpm/setup@v2
with:
cache: true
install: false
runtime: node@22.19.0
- run: pnpm install --frozen-lockfile
- name: Read pinned host CLI versions
id: pins
run: node scripts/host-cli-pins.mjs print
# The global npm prefix holding both CLIs (Claude Code and Codex ship
# platform-native binaries), keyed by OS, arch, and the exact pins
# (package name and version per host) so a deliberate re-pin of either
# misses the cache and installs fresh.
- name: Restore pinned host CLIs
id: host-cli-cache
uses: actions/cache@v6
with:
path: ${{ runner.temp }}/host-cli
key: host-cli-${{ runner.os }}-${{ runner.arch }}-${{ steps.pins.outputs.pins }}
- name: Install pinned host CLIs
if: steps.host-cli-cache.outputs.cache-hit != 'true'
run: node scripts/host-cli-pins.mjs install --prefix "$RUNNER_TEMP/host-cli"
# ubuntu runner: npm's global executables live in <prefix>/bin (the
# script's globalBinDirectory rule; Windows would be the prefix itself).
- name: Expose host CLIs on PATH
run: echo "$RUNNER_TEMP/host-cli/bin" >> "$GITHUB_PATH"
- name: Verify host CLI versions match the schema pins
run: node scripts/host-cli-pins.mjs verify
- run: pnpm build
- name: Host install proofs (source-built bundle)
run: pnpm test:host-install
- name: Host install proofs (packed tarball)
run: pnpm test:host-install:packed
- name: Packed Claude plugin validation proof
env:
AGENT_BUNDLE_PACKAGE_PREBUILT: '1'
run: pnpm test:packed:native
# Release-boundary scaffolder template matrix (mcp-server, cli-tool) plus
# the full packed pool — the nightly form of pre-publish `check:release`.
packed-matrix:
if: github.event_name == 'schedule'
name: Packed release matrix (Node 22.19)
runs-on: ubuntu-latest
timeout-minutes: 40
steps:
- uses: actions/checkout@v7
- uses: pnpm/setup@v2
with:
cache: true
install: false
runtime: node@22.19.0
- run: pnpm install --frozen-lockfile
# Reuse the runner image's Chrome; see the Verify job for the rationale.
- name: Ensure branded Chrome for Playwright
run: |
if command -v google-chrome >/dev/null 2>&1; then
echo "Using preinstalled $(google-chrome --version)"
else
pnpm exec playwright install --with-deps chrome
fi
- run: pnpm check:release
# Nightly evidence-capture pool: documentation-artifact browser
# journeys whose behavioral contracts are already proven per PR by the
# integration pool (see nightlyEvidenceTestFiles).
- run: pnpm test:evidence
rsc-runtime-micro-eval:
# Deterministic end-to-end spot-check of the built RSC runtime artifacts
# (hook -> RSC worker -> shared kernel state -> MCP tool lowering) without
# any real Claude/Codex host. Login-free real-host install proofs run in
# the host-install-proofs job; signed-in native smokes stay skip-gated in
# the manually dispatched native-host-smoke workflow on purpose.
needs: changes
if: >-
${{ !cancelled() && github.event_name != 'schedule' &&
(github.event_name != 'pull_request' || needs.changes.outputs.docs_only != 'true') }}
name: RSC runtime micro-eval (Node 22.19)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- uses: pnpm/setup@v2
with:
cache: true
install: false
runtime: node@22.19.0
- run: pnpm install --frozen-lockfile
- run: pnpm eval:spot
dependency-review:
if: github.event_name == 'pull_request'
name: Dependency review
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: actions/dependency-review-action@v5