Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/chore-drop-ard-config.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
'@ora-ai/ax': minor
'@ora-ai/ax-nextjs': minor
---

**Breaking (pre-1.0):** removed support for the legacy `ard.config.*` config file and its
Expand Down
2 changes: 1 addition & 1 deletion .changeset/phase-2-1-config.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
'@ora-ai/ax': minor
'@ora-ai/ax-nextjs': minor
---

Add Phase 2.1: `ax.config.*` (denylist/allowlist with a default-on
Expand Down
2 changes: 1 addition & 1 deletion .changeset/phase-2-8-gating-auth.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
'@ora-ai/ax': minor
'@ora-ai/ax-nextjs': minor
---

Add Phase 2.8: gating & auth. ax now reads each artifact's own auth declaration and emits a
Expand Down
30 changes: 30 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"mode": "pre",
"tag": "canary",
"initialVersions": {
"@ax-fixtures/bare": "0.0.0",
"@ax-fixtures/bare-js": "0.0.0",
"@ax-fixtures/config-overrides": "0.0.0",
"@ax-fixtures/deploy-variants": "0.0.0",
"@ax-fixtures/discovery": "0.0.0",
"@ax-fixtures/edge-cases": "0.0.0",
"@ax-fixtures/hybrid": "0.0.0",
"@ax-fixtures/llms-txt": "0.0.0",
"@ax-fixtures/markdown-twins": "0.0.0",
"@ax-fixtures/mcp-adapter": "0.0.0",
"@ax-fixtures/mcp-adapter-gated": "0.0.0",
"@ax-fixtures/mcp-multi-server": "0.0.0",
"@ax-fixtures/mdx-content": "0.0.0",
"@ax-fixtures/middleware": "0.0.0",
"@ax-fixtures/monorepo-root": "0.0.0",
"@ax-fixtures/monorepo-web": "0.0.0",
"@ax-fixtures/openapi": "0.0.0",
"@ax-fixtures/pages-bare": "0.0.0",
"@ax-fixtures/pages-mcp": "0.0.0",
"@ax-fixtures/pages-webmcp-declarative": "0.0.0",
"@ax-fixtures/webmcp-declarative": "0.0.0",
"@ax-fixtures/webmcp-imperative": "0.0.0",
"@ora-ai/ax-nextjs": "0.0.0"
},
"changesets": []
}
70 changes: 66 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,22 @@ jobs:
# lockfile is unchanged and short-circuits as "already up to date" without
# re-linking anything).
- name: Install ax's dependencies
run: pnpm install --filter ax --frozen-lockfile
run: pnpm install --filter @ora-ai/ax-nextjs --frozen-lockfile

- name: Build ax
run: pnpm --filter ax run build
run: pnpm --filter @ora-ai/ax-nextjs run build

# Now install everything else. dist/bin.js exists, so the fixtures' `ax`
# bin shims (needed for their `postbuild` step) link correctly this time.
- name: Install remaining dependencies
run: pnpm install --frozen-lockfile

# Every fixture must `next build` — that build also runs each fixture's `ax --report`
# postbuild, writing a machine-readable report to its `.ora/report.json`.
# postbuild, writing a machine-readable report to its `.ax/report.json`.
- name: Build fixtures
run: pnpm fixtures:build

# Report-snapshot layer: diff each freshly-built `.ora/report.json` against its committed,
# Report-snapshot layer: diff each freshly-built `.ax/report.json` against its committed,
# normalized golden (`fixtures/*/report.golden.json`). Proves the plugin's detections and
# recommendations are exactly what we expect for a real build of each fixture.
- name: Verify report snapshots
Expand All @@ -98,3 +98,65 @@ jobs:

- name: ARD conformance (official tool)
run: pnpm conformance

package:
name: pack + publint + attw
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --filter @ora-ai/ax-nextjs --frozen-lockfile

- name: Build
run: pnpm --filter @ora-ai/ax-nextjs run build

# Lints the publishable shape: exports map, files allowlist, main/types agreement.
- name: publint
run: pnpm dlx publint packages/ax

# Verifies TypeScript consumers actually resolve the declared types.
# esm-only profile: the package intentionally ships no CJS build.
- name: Are the types wrong
run: pnpm dlx @arethetypeswrong/cli --pack packages/ax --profile esm-only

- name: Pack tarball
run: pnpm --filter @ora-ai/ax-nextjs pack --pack-destination "$RUNNER_TEMP"

- uses: actions/upload-artifact@v4
with:
name: tarball
path: ${{ runner.temp }}/*.tgz
retention-days: 7

tarball-e2e:
name: tarball e2e (node ${{ matrix.node }})
needs: package
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# 18.18 is the floor `engines` claims; test exactly what we promise.
node: ['18.18', '20', '22']
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}

- uses: actions/download-artifact@v4
with:
name: tarball
path: ${{ runner.temp }}/tarball

# Plain node + npm on purpose: a consumer's environment, not the workspace's.
- name: Install packed tarball into a scratch app and build it
run: node scripts/tarball-e2e.mjs "$RUNNER_TEMP"/tarball/*.tgz
39 changes: 39 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Release

on:
push:
branches: [main]

concurrency: release-${{ github.ref }}

permissions:
contents: write # changesets/action pushes the version PR branch
pull-requests: write # ...and opens/updates the "Version Packages" PR
id-token: write # npm provenance (publishConfig.provenance) needs OIDC

jobs:
release:
name: version PR / publish
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

# Maintains the standing "Version Packages" PR from pending changesets;
# when that PR is merged, this same step builds and publishes instead.
- name: Version PR or publish
uses: changesets/action@v1
with:
publish: pnpm release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ fixtures/**/ax-manifest.js
# output — the tracked artifact is the normalized golden (fixtures/*/report.golden.json), which the
# report-snapshot layer diffs each freshly-built report against. The report itself embeds absolute
# checkout paths and a wall-clock timestamp, so it is never committed.
fixtures/**/.ora/
fixtures/**/.ax/

# npm pack build artifact (not committed in this repo; the demo repo commits its own copy)
*.tgz
Expand Down
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Era Labs

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
35 changes: 26 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# `@ora-ai/ax` — Agent Experience for Next.js
# `@ora-ai/ax-nextjs` — Agent Experience for Next.js

The web was built for humans. Now a new era begins: AI agents that browse, read, and act on a
site's behalf — and most sites are invisible to them: no map, no context, no way in.

`@ora-ai/ax` is the agent-experience toolkit for Next.js: a build step that generates and
`@ora-ai/ax-nextjs` is the agent-experience toolkit for Next.js: a build step that generates and
publishes your agent-facing artifacts, and an edge middleware that serves agents at runtime.

## One-time setup

```sh
npm install @ora-ai/ax # or: pnpm add @ora-ai/ax · yarn add @ora-ai/ax
npm install @ora-ai/ax-nextjs # or: pnpm add @ora-ai/ax-nextjs · yarn add @ora-ai/ax-nextjs
```

```sh
Expand All @@ -18,6 +18,23 @@ npx ax init

That's it. From now on, every build generates your agent-friendly artifacts automatically.

## Try it before release

Before `@ora-ai/ax-nextjs` is published, try it from a tarball instead of an npm install:

```sh
pnpm build # builds packages/ax (tsup → dist/); only dist ships
cd packages/ax && pnpm pack # writes ora-ai-ax-nextjs-0.0.0.tgz
cd /path/to/your-next-app
npm install /absolute/path/to/ora-ai-ax-nextjs-0.0.0.tgz # or a "file:" dependency in package.json
npx ax init # see "ax init" below
next build
```

Confirm it worked the way you would after a real release — check `/.well-known/ai-catalog.json`,
the `.md` twins, and `/404.md` (see "What ax does" below). Once `@ora-ai/ax-nextjs` is published,
drop the tarball step and install normally, as shown above in "One-time setup".

## What ax does

**Teach agents how to use your website — `agents.md`**
Expand Down Expand Up @@ -52,21 +69,21 @@ tree is opt-in, listed in `ax.config`, and never overwrites a file you already h

## The report

With `report: true` (the wizard's default), every build writes `.ora/report.json` — a
With `report: true` (the wizard's default), every build writes `.ax/report.json` — a
machine-readable summary of what was generated, detected, and skipped (and why), with every finding
mapped to [Ora](https://ora.ai)'s agent-readiness checks as `addressed` or `actionable`. Point your
coding agent at it after a build and let it work through what's left.

## How it works

A regular dependency, not a dev one: `@ora-ai/ax/middleware` is imported by your `middleware.ts`
A regular dependency, not a dev one: `@ora-ai/ax-nextjs/middleware` is imported by your `middleware.ts`
and bundled into the production build (the entry is Web-API-only with zero runtime dependencies, so
it's edge-safe). Skip the middleware and ax is build-time only — a devDependency works then too.

`ax init` wires two build hooks. `prebuild: ax manifest` builds a lightweight map of your site —
routes, gated paths, discovery artifacts — before Next.js compiles your middleware.
`postbuild: ax` uses the finished build to generate and publish the real artifacts against that map:
the catalog, markdown twins, scaffolds, and the report. At runtime, `@ora-ai/ax/middleware` reads
the catalog, markdown twins, scaffolds, and the report. At runtime, `@ora-ai/ax-nextjs/middleware` reads
the same map to serve agents directly. Wiring the middleware (and any JSON-LD component) into your
app is left to you — ax prints the exact lines to add, it never edits `middleware.ts` or your layout
itself.
Expand Down Expand Up @@ -126,7 +143,7 @@ Optional, loaded from your project root. Evaluated as real code (via
[`jiti`](https://github.com/unjs/jiti)), not parsed as static JSON.

```ts
import { defaultIsGated, type AxConfig } from '@ora-ai/ax';
import { defaultIsGated, type AxConfig } from '@ora-ai/ax-nextjs';

const config: AxConfig = {
// Your production origin. Falls back to SITE_URL / NEXT_PUBLIC_SITE_URL, then (on Vercel)
Expand All @@ -145,7 +162,7 @@ const config: AxConfig = {
// Generate markdown twins of your pages, /auth.md when surfaces are gated, and the /404.md
// wayfinding guide lost agents continue from. Default on.
markdownTwins: true,
// Write .ora/report.json, the machine-readable build report. Opt-in.
// Write .ax/report.json, the machine-readable build report. Opt-in.
report: true,
// Mark an artifact as gated behind auth so it's never advertised as open. Compose
// defaultIsGated to extend the built-in floor (which gates /api/auth/** and /api/webhooks/**)
Expand Down Expand Up @@ -179,7 +196,7 @@ actionable message.
## Repository layout

```
packages/ax the plugin / CLI (`@ora-ai/ax`) — the npm package (3 runtime deps: ajv, ajv-formats, jiti)
packages/ax the plugin / CLI (`@ora-ai/ax-nextjs`) — the npm package (3 runtime deps: ajv, ajv-formats, jiti)
spec/ vendored AI Catalog spec + hand-written JSON Schema + validator oracle
fixtures/* real Next.js apps — the flagship (a full demo-app fork) + single-axis fixtures; the test suite, docs examples, and eval corpus
```
52 changes: 52 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Releasing `@ora-ai/ax-nextjs`

Publishing is fully automated through [changesets](https://github.com/changesets/changesets)
and GitHub Actions (`.github/workflows/release.yml`). Nobody publishes from a laptop —
npm **provenance** is enabled, which only works from CI.

## One-time setup (npm org owner)

1. Create a **granular automation token** on npmjs.com with read/write access to the
`@ora-ai` scope (Profile → Access Tokens → Generate New Token → Granular).
2. Add it to this repo as the `NPM_TOKEN` Actions secret
(Settings → Secrets and variables → Actions).

That's it. The first publish creates the `@ora-ai/ax-nextjs` package on the registry.

## How a release happens

1. Every feature PR includes a changeset (`pnpm changeset`) declaring its bump
(patch/minor/major) and a human-readable summary.
2. On every push to `main`, the Release workflow maintains a standing
**"Version Packages" PR** that rolls up all pending changesets into a version
bump + CHANGELOG entry.
3. **Merging that PR is the release.** The workflow builds and runs
`changeset publish`, which publishes to npm with provenance.

## Canary line (current state)

The repo is in changesets **pre-release mode** (`.changeset/pre.json`) with the
`canary` tag. Versions publish as `0.1.0-canary.N` under the **`canary` dist-tag**,
so a plain `npm install @ora-ai/ax-nextjs` resolves nothing until a real `latest`
exists — only an explicit `@canary` install gets the prerelease.

Install for partners: `npm install @ora-ai/ax-nextjs@canary`

## Promoting canary → latest

When a canary has proven itself (Phase 6 of the plan):

```sh
pnpm changeset pre exit
git commit -am "chore: exit canary pre-release mode"
```

Merge that to `main`; the next "Version Packages" PR produces a stable version
(e.g. `0.1.0`) which publishes under `latest`.

## Ground rules

- Strict semver: catalog output changes ≥ minor; breaking config changes = major;
ARD spec-version bumps called out explicitly in release notes.
- The `files` allowlist in `packages/ax/package.json` is `["dist"]` — check the
tarball (`pnpm --filter @ora-ai/ax-nextjs pack`) before promoting to `latest`.
2 changes: 1 addition & 1 deletion docs-internal/PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -1575,7 +1575,7 @@ the existing deploy steps.
| 4 | Schema strategy: evaluate exported schemas vs parse AST? | Evaluate (subprocess) | _pending_ |
| 5 | Drift diff ships in v1? | Yes, informational-only (it's nearly free) | _pending_ |
| 6 | Emission default: static file vs route handler? | Static file default, route handler for `basePath` | _pending_ |
| 7 | Package name / npm scope / who owns publish rights? | — | **Resolved (2026-07-27):** `@ora-ai/ax`, CLI bin `ax` — "AX" (Agent Experience) is the product story; scoped name avoids npm collisions. Publish rights: Ora's npm org (create `@ora-ai` if absent). Repo/fixture scopes renamed accordingly (`@ax-fixtures/*`). |
| 7 | Package name / npm scope / who owns publish rights? | — | **Resolved (2026-07-27):** `@ora-ai/ax`, CLI bin `ax` — "AX" (Agent Experience) is the product story; scoped name avoids npm collisions. Publish rights: Ora's npm org (create `@ora-ai` if absent). Repo/fixture scopes renamed accordingly (`@ax-fixtures/*`). **Re-resolved (2026-08-26):** `@ora-ai/ax` was claimed on npm on 2026-08-14 by the Ora team's own CLI (published from `eralabs-ai/ora-cli`), so this package is now **`@ora-ai/ax-nextjs`**; the CLI bin stays `ax` (bin names don't collide with package names). Publishing is owned by the Ora team. |
| 8 | Real-LLM eval budget + which model/provider? | Nightly + pre-release only | _pending_ |
| 9 | Timeline expectations per phase? | Skeleton wk 1; Phases 2–3 are the bulk | _pending_ |
| 10 | Which artifacts should the plugin emit/reference? | **Resolved (2026-07-16):** Ora confirmed the crawler ingests the first-party `/.well-known/ai-catalog.json`, `openapi.json`, `/graphql`, and `llms.txt`. The plugin emits the Next-idiomatic subset — MCP + `public/openapi.json` + config-declared docs/skills now; WebMCP + `llms.txt` generation next; **GraphQL out** (not idiomatic Next). Sitemap = detect + recommend `next-sitemap`, don't reimplement. | **confirmed** |
Expand Down
2 changes: 1 addition & 1 deletion fixtures/bare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@types/node": "^20.17.12",
"@types/react": "^19.0.7",
"@types/react-dom": "^19.0.3",
"@ora-ai/ax": "workspace:*",
"@ora-ai/ax-nextjs": "workspace:*",
"typescript": "^5.7.3"
}
}
Loading
Loading