From c60b841c13fc828ad7d97388a8d901996c0bec09 Mon Sep 17 00:00:00 2001 From: Ariel Shulman Date: Wed, 26 Aug 2026 13:07:43 +0300 Subject: [PATCH 1/3] chore: rename to @ora-ai/ax-nextjs and prepare for npm publishing The planned name @ora-ai/ax was claimed on npm (2026-08-14) by the Ora team's own CLI (eralabs-ai/ora-cli), so the package is now @ora-ai/ax-nextjs; the CLI bin stays `ax`. Golden reports regenerated: the longer generated-by marker shifts twin byte/token counts. Publish readiness on top of the rename: - package.json: repository/homepage/bugs/keywords (repository is required for npm provenance), Next peer narrowed to >=15 (the only major CI builds against) - MIT LICENSE (Era Labs) at the root and in the package dir; README copied into packages/ax so the npm page and tarball carry one - release.yml: changesets version-PR/publish flow from CI with provenance; RELEASING.md documents the NPM_TOKEN setup and the canary -> latest promotion - changesets pre-release mode entered with tag `canary`, so the pending changesets produce 0.1.0-canary.0 under the canary dist-tag - scripts/tarball-e2e.mjs + CI jobs: pack the tarball, publint + attw it, then install it into a scratch Next app with plain npm and run a real build on Node 18.18/20/22 to back the engines claim - ci.yml: `--filter ax` -> `--filter @ora-ai/ax-nextjs` (name-based filters stopped matching after the rename) Co-Authored-By: Claude Fable 5 --- .changeset/chore-drop-ard-config.md | 2 +- .changeset/phase-2-1-config.md | 2 +- .changeset/phase-2-8-gating-auth.md | 2 +- .changeset/pre.json | 30 + .github/workflows/ci.yml | 66 +- .github/workflows/release.yml | 39 ++ LICENSE | 21 + README.md | 16 +- RELEASING.md | 52 ++ docs-internal/PLAN.md | 2 +- fixtures/bare/package.json | 2 +- fixtures/bare/report.golden.json | 6 +- fixtures/config-overrides/ax.config.ts | 4 +- fixtures/config-overrides/package.json | 2 +- fixtures/config-overrides/report.golden.json | 6 +- fixtures/deploy-variants/package.json | 2 +- fixtures/deploy-variants/report.golden.json | 6 +- fixtures/discovery/ax.config.ts | 2 +- fixtures/discovery/package.json | 2 +- fixtures/discovery/report.golden.json | 12 +- fixtures/discovery/twins.golden/index.md | 2 +- fixtures/hybrid/ax.config.ts | 2 +- fixtures/hybrid/next-env.d.ts | 2 + fixtures/hybrid/package.json | 2 +- fixtures/hybrid/report.golden.json | 6 +- fixtures/llms-txt/ax.config.ts | 2 +- fixtures/llms-txt/package.json | 2 +- fixtures/llms-txt/report.golden.json | 6 +- fixtures/markdown-twins/ax.config.ts | 2 +- fixtures/markdown-twins/package.json | 2 +- fixtures/markdown-twins/report.golden.json | 24 +- .../twins.golden/guides/setup.md | 2 +- fixtures/markdown-twins/twins.golden/index.md | 2 +- fixtures/markdown-twins/twins.golden/live.md | 2 +- fixtures/mcp-adapter-gated/ax.config.ts | 2 +- fixtures/mcp-adapter-gated/package.json | 2 +- fixtures/mcp-adapter-gated/report.golden.json | 12 +- .../mcp-adapter-gated/twins.golden/auth.md | 2 +- fixtures/mcp-adapter/ax.config.ts | 2 +- fixtures/mcp-adapter/package.json | 2 +- fixtures/mcp-adapter/report.golden.json | 6 +- fixtures/mcp-multi-server/ax.config.ts | 2 +- fixtures/mcp-multi-server/package.json | 2 +- fixtures/mcp-multi-server/report.golden.json | 18 +- .../mcp-multi-server/twins.golden/auth.md | 2 +- .../mcp-multi-server/twins.golden/index.md | 2 +- fixtures/mdx-content/ax.config.ts | 2 +- fixtures/mdx-content/package.json | 2 +- fixtures/mdx-content/report.golden.json | 18 +- fixtures/mdx-content/twins.golden/guide.md | 2 +- fixtures/mdx-content/twins.golden/index.md | 2 +- fixtures/middleware/README.md | 2 +- fixtures/middleware/app/layout.tsx | 3 +- fixtures/middleware/ax.config.ts | 2 +- fixtures/middleware/middleware.ts | 2 +- fixtures/middleware/package.json | 2 +- fixtures/middleware/report.golden.json | 6 +- fixtures/middleware/twins.golden/index.md | 4 +- fixtures/openapi/ax.config.ts | 2 +- fixtures/openapi/package.json | 2 +- fixtures/openapi/report.golden.json | 12 +- fixtures/openapi/twins.golden/auth.md | 2 +- fixtures/pages-bare/ax.config.ts | 2 +- fixtures/pages-bare/next-env.d.ts | 3 +- fixtures/pages-bare/package.json | 2 +- fixtures/pages-bare/report.golden.json | 6 +- fixtures/pages-mcp/ax.config.ts | 2 +- fixtures/pages-mcp/next-env.d.ts | 3 +- fixtures/pages-mcp/package.json | 2 +- fixtures/pages-mcp/report.golden.json | 6 +- .../pages-webmcp-declarative/ax.config.ts | 2 +- .../pages-webmcp-declarative/next-env.d.ts | 3 +- .../pages-webmcp-declarative/package.json | 2 +- .../report.golden.json | 6 +- fixtures/webmcp-declarative/ax.config.ts | 2 +- fixtures/webmcp-declarative/package.json | 2 +- .../webmcp-declarative/report.golden.json | 6 +- packages/ax/LICENSE | 21 + packages/ax/README.md | 566 ++++++++++++++++++ packages/ax/package.json | 21 +- packages/ax/src/init-config.ts | 4 +- packages/ax/src/markdown-artifact.ts | 4 +- packages/ax/src/middleware-wiring.ts | 2 +- packages/ax/src/middleware/index.ts | 2 +- packages/ax/src/report.ts | 2 +- packages/ax/src/scaffold-robots.ts | 4 +- packages/ax/test/born-passing.test.ts | 2 +- packages/ax/test/cli.test.ts | 2 +- packages/ax/test/init-config.test.ts | 6 +- packages/ax/test/middleware-wiring.test.ts | 4 +- packages/ax/test/scaffold-robots.test.ts | 4 +- pnpm-lock.yaml | 34 +- scripts/clean-fixture-outputs.mjs | 2 +- scripts/dogfood-middleware.mjs | 2 +- scripts/tarball-e2e.mjs | 84 +++ scripts/verify-report-snapshots.mjs | 2 +- 96 files changed, 1084 insertions(+), 186 deletions(-) create mode 100644 .changeset/pre.json create mode 100644 .github/workflows/release.yml create mode 100644 LICENSE create mode 100644 RELEASING.md create mode 100644 packages/ax/LICENSE create mode 100644 packages/ax/README.md create mode 100644 scripts/tarball-e2e.mjs diff --git a/.changeset/chore-drop-ard-config.md b/.changeset/chore-drop-ard-config.md index ad85117..987ead6 100644 --- a/.changeset/chore-drop-ard-config.md +++ b/.changeset/chore-drop-ard-config.md @@ -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 diff --git a/.changeset/phase-2-1-config.md b/.changeset/phase-2-1-config.md index c6b7e68..949a741 100644 --- a/.changeset/phase-2-1-config.md +++ b/.changeset/phase-2-1-config.md @@ -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 diff --git a/.changeset/phase-2-8-gating-auth.md b/.changeset/phase-2-8-gating-auth.md index 81d516f..bb767e6 100644 --- a/.changeset/phase-2-8-gating-auth.md +++ b/.changeset/phase-2-8-gating-auth.md @@ -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 diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 0000000..4ca6483 --- /dev/null +++ b/.changeset/pre.json @@ -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": [] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4768af1..3602873 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,10 +56,10 @@ 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. @@ -91,3 +91,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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..f937c59 --- /dev/null +++ b/.github/workflows/release.yml @@ -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 }} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6ae40b7 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md index 4a8ef41..98c432d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# `@ora-ai/ax` — Agent Experience for Next.js +# `@ora-ai/ax-nextjs` — Agent Experience for Next.js AI agents are becoming every site's newest user segment, and most sites are invisible to them. `ax` is one `postbuild` line that makes a Next.js app **discoverable, legible, and usable by agents**: ```sh -npm install --save-dev @ora-ai/ax +npm install --save-dev @ora-ai/ax-nextjs ``` ```json @@ -130,7 +130,7 @@ This matrix is a public contract from day one. Anything outside it is out of sco ## 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/* minimal-but-real Next.js apps — the test suite, docs examples, and eval corpus ``` @@ -187,7 +187,7 @@ tool that reads it, so the file you commit says plainly which tool it configures > instruction rather than silently building with defaults, so this is safe to miss and easy to fix. ```ts -import { defaultIsGated, type AxConfig } from '@ora-ai/ax'; +import { defaultIsGated, type AxConfig } from '@ora-ai/ax-nextjs'; const config: AxConfig = { // Your production origin — every detected entry's URL is resolved against this. Optional: falls @@ -329,7 +329,7 @@ whether you actually have a sitemap. So: - **You have a `public/robots.txt`** → ax _appends_ a `Sitemap:` line (only when a sitemap really exists) and an `Agentmap:` line pointing at the generated catalog, in a block marked - `# Added by @ora-ai/ax`, and only when they're missing. Existing lines are never modified or + `# Added by @ora-ai/ax-nextjs`, and only when they're missing. Existing lines are never modified or reordered, a directive you already wrote counts as written (in any casing), and running twice appends nothing the second time. - **You have an `app/robots.ts` route** → ax doesn't touch it. That file is code, and it owns your @@ -365,7 +365,7 @@ nothing, and the report keeps saying so. Agents read markdown better than HTML, and the `.md`-URL convention (`/docs` → `/docs.md`) is the one retrieval mechanism that needs **zero runtime**: ax writes each twin as a static file in `public/`, so Next serves it as-is before any middleware ships. Twins are **generated artifacts, -not scaffolds** — regenerated every build, marked `generated-by: "@ora-ai/ax"` in their +not scaffolds** — regenerated every build, marked `generated-by: "@ora-ai/ax-nextjs"` in their frontmatter, never yours to edit (edit a page's twin and your edits are one build from gone; if a human should own the markdown, make it a real markdown source instead — see Tier 1). @@ -427,7 +427,7 @@ postbuild, so the manifest is regenerated by a fast, source-tree-only `prebuild` wires `"prebuild": "ax manifest"` (never touching an existing prebuild). A full `ax` run also refreshes an existing manifest module, but never creates one you didn't opt into. -### The runtime middleware — `@ora-ai/ax/middleware` +### The runtime middleware — `@ora-ai/ax-nextjs/middleware` The negotiation half of the markdown story: detected AI agents — and any client sending `Accept: text/markdown` — receive the markdown ax generated, without you writing serving logic. @@ -437,7 +437,7 @@ your middleware: ```ts // middleware.ts — the exact wiring the CLI prints; run `npx ax manifest` first -import { withAx } from '@ora-ai/ax/middleware'; +import { withAx } from '@ora-ai/ax-nextjs/middleware'; import { axManifest } from './ax-manifest'; export default withAx({ manifest: axManifest }); // or withAx({ manifest: axManifest }, existingMiddleware) diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..b3d04e5 --- /dev/null +++ b/RELEASING.md @@ -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 --save-dev @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`. diff --git a/docs-internal/PLAN.md b/docs-internal/PLAN.md index 6f69114..97a0ea8 100644 --- a/docs-internal/PLAN.md +++ b/docs-internal/PLAN.md @@ -1562,7 +1562,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** | diff --git a/fixtures/bare/package.json b/fixtures/bare/package.json index 05dbe7d..599ba41 100644 --- a/fixtures/bare/package.json +++ b/fixtures/bare/package.json @@ -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" } } diff --git a/fixtures/bare/report.golden.json b/fixtures/bare/report.golden.json index 98f3d36..91669ec 100644 --- a/fixtures/bare/report.golden.json +++ b/fixtures/bare/report.golden.json @@ -139,13 +139,13 @@ "id": "markdown-negotiation", "artifact": "middleware", "status": "actionable", - "note": "Serve agents the generated markdown automatically: First run `npx ax manifest` (and wire it as the \"prebuild\" script so it stays fresh — `middleware.ts` is compiled during `next build`, so the manifest must exist before the build starts). Then: add `import { withAx } from '@ora-ai/ax/middleware';` and `import { axManifest } from './ax-manifest';`, create middleware.ts with `export default withAx({ manifest: axManifest });`, and export the matcher literal `export const config = { matcher: ['/((?!_next|api|.*\\\\..*|favicon|robots|health|status).*)'] };`. The middleware rewrites a request to its markdown twin only when the manifest lists one, never touches gated paths, and answers unknown URLs from detected agents with a 200 markdown wayfinding body — it composes with (never replaces) your existing middleware." + "note": "Serve agents the generated markdown automatically: First run `npx ax manifest` (and wire it as the \"prebuild\" script so it stays fresh — `middleware.ts` is compiled during `next build`, so the manifest must exist before the build starts). Then: add `import { withAx } from '@ora-ai/ax-nextjs/middleware';` and `import { axManifest } from './ax-manifest';`, create middleware.ts with `export default withAx({ manifest: axManifest });`, and export the matcher literal `export const config = { matcher: ['/((?!_next|api|.*\\\\..*|favicon|robots|health|status).*)'] };`. The middleware rewrites a request to its markdown twin only when the manifest lists one, never touches gated paths, and answers unknown URLs from detected agents with a 200 markdown wayfinding body — it composes with (never replaces) your existing middleware." }, { "id": "markdown-negotiation-vary", "artifact": "middleware", "status": "actionable", - "note": "Serve agents the generated markdown automatically: First run `npx ax manifest` (and wire it as the \"prebuild\" script so it stays fresh — `middleware.ts` is compiled during `next build`, so the manifest must exist before the build starts). Then: add `import { withAx } from '@ora-ai/ax/middleware';` and `import { axManifest } from './ax-manifest';`, create middleware.ts with `export default withAx({ manifest: axManifest });`, and export the matcher literal `export const config = { matcher: ['/((?!_next|api|.*\\\\..*|favicon|robots|health|status).*)'] };`. The middleware rewrites a request to its markdown twin only when the manifest lists one, never touches gated paths, and answers unknown URLs from detected agents with a 200 markdown wayfinding body — it composes with (never replaces) your existing middleware." + "note": "Serve agents the generated markdown automatically: First run `npx ax manifest` (and wire it as the \"prebuild\" script so it stays fresh — `middleware.ts` is compiled during `next build`, so the manifest must exist before the build starts). Then: add `import { withAx } from '@ora-ai/ax-nextjs/middleware';` and `import { axManifest } from './ax-manifest';`, create middleware.ts with `export default withAx({ manifest: axManifest });`, and export the matcher literal `export const config = { matcher: ['/((?!_next|api|.*\\\\..*|favicon|robots|health|status).*)'] };`. The middleware rewrites a request to its markdown twin only when the manifest lists one, never touches gated paths, and answers unknown URLs from detected agents with a 200 markdown wayfinding body — it composes with (never replaces) your existing middleware." } ] }, @@ -158,6 +158,6 @@ "No agents.md found — consider adding one (public/agents.md) with when-to-use / when-NOT-to-use guidance for agents. ax won't write its content for you; the companion skill can help author it from your repo.", "No JSON-LD structured data found in your layouts/pages — structured data is how registries and agents disambiguate and rank your site. Add an Organization block in your root layout (