name: CI on: push: branches: [main] pull_request: branches: [main] permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: lint: runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - run: npm ci --ignore-scripts - run: npm run lint test: runs-on: ubuntu-latest timeout-minutes: 15 strategy: matrix: node-version: [20, 22, 24] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: npm - run: npm ci --ignore-scripts - run: npm test coverage: runs-on: ubuntu-latest timeout-minutes: 15 permissions: contents: read id-token: write steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - run: npm ci --ignore-scripts - run: npm run test:coverage - uses: codecov/codecov-action@v5 with: use_oidc: true files: ./packages/core/coverage/lcov.info fail_ci_if_error: true package: runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - run: npm ci --ignore-scripts - run: npm run build - name: Smoke-test that the built entry points import and the CLI runs working-directory: packages/core run: | node -e "Promise.all([import('./dist/index.js'), import('./dist/observability/index.js'), import('./dist/observability/file.js'), import('./dist/acp.js'), import('./dist/process.js'), import('./dist/mcp.js'), import('./dist/ai-sdk.js'), import('./dist/eval/index.js'), import('./dist/eval/file.js')]).then(() => console.log('entry points import OK')).catch((e) => { console.error(e); process.exit(1); })" node dist/cli/oma.js help | grep -q "oma eval run" node dist/cli/oma.js help | grep -q "oma eval gate" echo "CLI help runs OK" - name: Run the no-network evaluation gate pass and fail paths working-directory: packages/core run: | eval_gate_out=$(mktemp -d) node dist/cli/oma.js eval run \ --set tests/fixtures/eval/set.json \ --target tests/fixtures/eval/target.mjs \ --gate tests/fixtures/eval/gate.json \ --baseline tests/fixtures/eval/baseline.json \ --out "$eval_gate_out/pass" > "$eval_gate_out/pass-summary.json" jq -e '.verdict.pass == true' "$eval_gate_out/pass-summary.json" set +e node dist/cli/oma.js eval run \ --set tests/fixtures/eval/set.json \ --target tests/fixtures/eval/target-gate-fail.mjs \ --gate tests/fixtures/eval/gate.json \ --baseline tests/fixtures/eval/baseline.json \ --out "$eval_gate_out/fail" > "$eval_gate_out/fail-summary.json" eval_gate_status=$? set -e test "$eval_gate_status" -eq 1 jq -e '.verdict.pass == false' "$eval_gate_out/fail-summary.json" - name: Assert the create-oma-app template pins the current core version run: | core_version=$(jq -r .version packages/core/package.json) for manifest in packages/create-oma-app/templates/*/package.json; do pinned=$(jq -r '.dependencies["@open-multi-agent/core"]' "$manifest") if [ "$pinned" != "$core_version" ]; then echo "$manifest pins @open-multi-agent/core@$pinned but this repo's core is $core_version." exit 1 fi done - name: Assert the published dependency blocks stay semver ranges run: | set -euo pipefail # save-exact=true in the root .npmrc pins whatever `npm install` # saves. That is right for the tree that never leaves this # repository and wrong for the blocks a consumer installs: an exact # version there overrides their own resolution and stops their # `npm audit fix` from reaching a patched build until we cut a # release. A new dependency added to one of these has to be widened # by hand, so assert it rather than trusting the habit. fail=0 for manifest in packages/core/package.json packages/otel/package.json packages/create-oma-app/package.json; do for block in dependencies peerDependencies; do pinned=$(jq -r --arg b "$block" '(.[$b] // {}) | to_entries[] | select(.value | test("^[0-9]")) | "\(.key)@\(.value)"' "$manifest" | tr '\n' ' ') if [ -n "$pinned" ]; then echo "::error::$manifest .$block ships to consumers and must stay semver ranges, but these are exact: $pinned" fail=1 fi done done test "$fail" -eq 0 - name: Typecheck the create-oma-app template against core run: npm run typecheck:template -w create-oma-app - name: Assert the npm tarball ships exactly the expected files working-directory: packages/core run: | paths=$(npm pack --dry-run --json | jq -r '.[0].files[].path') unexpected=$(echo "$paths" | grep -vE '^(dist/|README\.md$|LICENSE$|package\.json$)' || true) if [ -n "$unexpected" ]; then echo "Unexpected files staged for the npm tarball:" echo "$unexpected" echo "Only dist/, README.md, LICENSE, package.json may ship." exit 1 fi missing="" for f in dist/index.js dist/index.d.ts dist/observability/index.js dist/observability/index.d.ts dist/observability/file.js dist/observability/file.d.ts dist/cli/oma.js dist/acp.js dist/acp.d.ts dist/process.js dist/process.d.ts dist/mcp.js dist/mcp.d.ts dist/ai-sdk.js dist/ai-sdk.d.ts dist/eval/index.js dist/eval/index.d.ts dist/eval/file.js dist/eval/file.d.ts; do echo "$paths" | grep -qxF "$f" || missing="$missing $f" done if [ -n "$missing" ]; then echo "Required entry points missing from the npm tarball:$missing" echo "Either the build did not run or an entry was dropped from package.json \"files\"." exit 1 fi - name: Smoke-test the installed core npm bin working-directory: packages/core run: | pack_dir=$(mktemp -d) consumer_dir=$(mktemp -d) pack_json=$(npm pack --json --pack-destination "$pack_dir") tarball=$(echo "$pack_json" | jq -r '.[0].filename') npm install --prefix "$consumer_dir" "$pack_dir/$tarball" help_output=$("$consumer_dir/node_modules/.bin/oma" help) echo "$help_output" | grep -q "oma eval run" echo "$help_output" | grep -q "oma eval gate" echo "Installed CLI help runs OK" - name: Assert the create-oma-app tarball ships dist + template working-directory: packages/create-oma-app run: | paths=$(npm pack --dry-run --json | jq -r '.[0].files[].path') unexpected=$(echo "$paths" | grep -vE '^(dist/|template/|templates/|README\.md$|LICENSE$|package\.json$)' || true) if [ -n "$unexpected" ]; then echo "Unexpected files staged for the create-oma-app tarball:" echo "$unexpected" echo "Only dist/, template/, README.md, LICENSE, package.json may ship." exit 1 fi missing="" for f in dist/index.js dist/scaffold.js templates/demo/package.json templates/pr-review/src/index.ts templates/security/src/index.ts template/src/runtime.ts template/src/report.ts template/_env.example template/_env.ollama template/_gitignore; do echo "$paths" | grep -qxF "$f" || missing="$missing $f" done if [ -n "$missing" ]; then echo "Required files missing from the create-oma-app tarball:$missing" echo "The scaffolder needs its template/ to function once published." exit 1 fi - name: Smoke-test that the otel entry point imports working-directory: packages/otel run: node -e "import('./dist/index.js').then(() => console.log('otel entry point imports OK')).catch((e) => { console.error(e); process.exit(1); })" - name: Assert the otel tarball ships exactly the expected files working-directory: packages/otel run: | paths=$(npm pack --dry-run --json | jq -r '.[0].files[].path') unexpected=$(echo "$paths" | grep -vE '^(dist/|README\.md$|LICENSE$|package\.json$)' || true) if [ -n "$unexpected" ]; then echo "Unexpected files staged for the @open-multi-agent/otel tarball:" echo "$unexpected" echo "Only dist/, README.md, LICENSE, package.json may ship." exit 1 fi missing="" for f in dist/index.js dist/index.d.ts; do echo "$paths" | grep -qxF "$f" || missing="$missing $f" done if [ -n "$missing" ]; then echo "Required entry points missing from the @open-multi-agent/otel tarball:$missing" echo "Either the build did not run or an entry was dropped from package.json \"files\"." exit 1 fi - name: Run public observability examples without network or API keys run: npm run test:observability-examples - name: Run public evaluation example without network or API keys run: npm run test:eval-examples - name: Run tolerant same-host observability regression gate run: npm run bench:observability:ci - name: Pack the minimum core version supported by OTel id: minimum-otel-core run: | set -euo pipefail range=$(jq -r '.dependencies["@open-multi-agent/core"]' packages/otel/package.json) if [[ ! "$range" =~ ^\^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "::error::expected an exact caret range for the OTel core dependency; got $range" exit 1 fi minimum="${range#^}" filename=$(npm pack "@open-multi-agent/core@$minimum" --pack-destination "$RUNNER_TEMP" --json | jq -er '.[0].filename') tarball="$RUNNER_TEMP/$filename" test -f "$tarball" echo "tarball=$tarball" >> "$GITHUB_OUTPUT" - name: Install real core and OTel tarballs in clean consumers env: OMA_OBSERVABILITY_MIN_CORE_TARBALL: ${{ steps.minimum-otel-core.outputs.tarball }} run: npm run test:observability-pack scaffold-e2e: runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - run: npm ci --ignore-scripts - name: End-to-end scaffold smoke test (pack -> generate -> install -> run gate) run: npm run test:scaffold