diff --git a/.changeset/docs-site-readme-link.md b/.changeset/docs-site-readme-link.md new file mode 100644 index 000000000..776fe5713 --- /dev/null +++ b/.changeset/docs-site-readme-link.md @@ -0,0 +1,5 @@ +--- +"agent-bundle": patch +--- + +Link the package README to the hosted documentation site so `npm` readers can find the full guide, configuration, host, event, notice, and diagnostics references. The pinned Cursor and portable capability tables now record Cursor's `.cursor-plugin/marketplace.json` path and the portable `${PLUGIN_ROOT}`/`${PLUGIN_DATA}` tokens, so the generated host matrix matches what the adapters emit. (#384) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac06832a5..b1f809de8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,11 +22,12 @@ concurrency: 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. + # PR-only docs allowlist: docs/**, agent-patterns/**, website/** (the + # Rspress site, validated by docs.yml), .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 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000..312b6bfb6 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,98 @@ +name: Docs + +# Validates the Rspress documentation site (website/) on every pull request +# and push to main, and deploys website/doc_build to GitHub Pages from main +# (on push, or on a manual dispatch of the main branch). +# +# No `paths:` filter on purpose: package-source changes can break TypeDoc +# generation and the generated reference pages without touching website/**, +# and a path-filtered workflow can never safely become a required check. +# Website-only PRs skip the heavy jobs in ci.yml (scripts/classify-docs-only.mjs +# treats website/** as docs-only) because this workflow covers them. +# +# One-time prerequisite: repository Settings -> Pages -> Source must be +# "GitHub Actions" (REST: build_type=workflow), or deploy-pages fails. + +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +# PR pushes cancel the superseded run of the same PR. Every run that can +# deploy (push or manual dispatch on main) shares one non-cancelling group so +# build-and-deploy runs execute in order and an older, slower build can never +# deploy over a newer one (GitHub keeps at most one pending run per group, so +# a superseded pending build is dropped, never reordered). Dispatches on other +# refs get SHA-keyed groups. +concurrency: + group: docs-${{ github.ref == 'refs/heads/main' && github.event_name != 'pull_request' && 'main' || github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + build: + name: Docs site (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 + # Website-only PRs skip every ci.yml job that lints, so lint here too. + - run: pnpm lint + # Typecheck the site config and build it. Rspress's own dead-link, + # dead-anchor, dead-image, and language-parity checks fail the build if + # TypeDoc or the generated reference pages are missing, since the + # authored pages link to them. + - run: pnpm docs:site:build + - name: Upload Pages artifact + if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') + uses: actions/upload-pages-artifact@v5 + with: + path: website/doc_build + + deploy: + name: Deploy to GitHub Pages + needs: build + if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + # Never cancel an in-flight Pages deployment. + concurrency: + group: pages + cancel-in-progress: false + steps: + # Re-running an older main run (or any path that bypasses the queue + # above) must not publish a stale site: only deploy when this commit is + # still the head of main. + - name: Confirm this commit is still the head of main + id: head + env: + GH_TOKEN: ${{ github.token }} + run: | + head="$(gh api "repos/${GITHUB_REPOSITORY}/branches/main" --jq .commit.sha)" + if [ "$head" = "$GITHUB_SHA" ]; then + echo "deploy=true" >> "$GITHUB_OUTPUT" + else + echo "deploy=false" >> "$GITHUB_OUTPUT" + echo "Skipped deploy: main moved to ${head}; this run built ${GITHUB_SHA}." >> "$GITHUB_STEP_SUMMARY" + fi + - if: steps.head.outputs.deploy == 'true' + uses: actions/configure-pages@v6 + - if: steps.head.outputs.deploy == 'true' + id: deployment + uses: actions/deploy-pages@v5 diff --git a/.gitignore b/.gitignore index 7d6d8ee5a..9b790cb4e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,12 @@ packages/*/NOTICE # Aborted runtime-playground fixture workspaces .runtime-playground-*/ + +website/doc_build/ +website/docs/en/api/**/*.md +website/docs/zh/api/**/*.md +# Build-time reference pages rendered from capability tables and docs/diagnostics.md +website/docs/*/reference/hosts.md +website/docs/*/reference/events.md +website/docs/*/reference/notices.md +website/docs/*/reference/diagnostics.md diff --git a/AGENTS.md b/AGENTS.md index 6be02cbf9..426198894 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,36 @@ - Never accept or capture a Workbench route while its loading state is still visible. - Browser acceptance must cover populated state plus the documented stale-diagnostic and repair flow. +## Documentation site + +- `website/` is the public Rspress docsite + (), deployed from `main` + by `.github/workflows/docs.yml`. It is user-facing product, held to the + same accuracy bar as the code it describes. +- A PR that adds or changes user-facing behavior — a CLI command or flag, + config key, public export or entry point, hook event or result rule, host + target or artifact, diagnostic code, environment variable, or example — + updates the matching page under `website/docs/en/**` **and** its + `website/docs/zh/**` translation in the same PR. Ask the same question + when writing the changeset: if the summary is user-facing, the docs almost + certainly need the same change. Internal refactors, tests, and CI do not. +- Reference prose must match the source. State what the code does, not what + the design intended; when unsure, read the adapter or validator before + writing the sentence. Where the generated pages and hand-written pages + disagree, the generated pages are right — fix the hand-written one. +- Never hand-edit generated pages: `website/docs/{en,zh}/api/**` comes from + TypeDoc, and the hosts, events, and diagnostics reference pages are + rendered at build time from `packages/agent-bundle/src/adapters/capabilities/*.json` + and `docs/diagnostics.md`. Change the source, and the site follows. +- `pnpm docs:site:build` is the gate: typecheck, build, and Rspress's + dead-link, dead-anchor, dead-image, and language-parity checks. Parity + fails the build if one locale gains a page the other lacks. Run it before + pushing anything under `website/`, and after any change to public + exports, since TypeDoc compiles `packages/agent-bundle/src` directly. +- The site is desktop-first, like the Workbench. Wide tables scroll; code + samples wrap at roughly 90 columns so they render without horizontal + overflow at the default content width. + ## Changesets - Every PR that changes a publishable package (`packages/agent-bundle`, diff --git a/README.md b/README.md index b5199ab46..0d034428e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ agent-bundle compiles an agent plugin — skills, hooks, MCP servers, and scripts, described by one typed config — into installable artifacts for Claude Code, Codex, and Cursor, plus a portable layout. You write the plugin once; the compiler emits each host's manifests and wrappers. +Documentation: [scriptedalchemy.github.io/agent-bundle](https://scriptedalchemy.github.io/agent-bundle/) (English and 简体中文; guide, reference, generated type API, host capability matrices). + Requires Node.js 22.19 or later. ## Install diff --git a/docs/diagnostics.md b/docs/diagnostics.md index db5a7e85b..f9d8e6ca3 100644 --- a/docs/diagnostics.md +++ b/docs/diagnostics.md @@ -2,9 +2,12 @@ Every agent-bundle failure or nudge is one structured diagnostic: a stable `code` (`AB` + four digits), a `severity` (`error`, `warning`, or `info`), a -`message`, and usually a `sourcePath` and a `recovery` hint. Commands exit -nonzero only when an **error** diagnostic is present; warnings and infos never -gate a build, a validation, or a dev rebuild. +`message`, and usually a `sourcePath` and a `recovery` hint. The +diagnostic-gated commands (`build`, `prepack`, `validate`, `doctor`, `install`, +`dev`) exit nonzero only when an **error** diagnostic is present; warnings and +infos never gate a build, a validation, or a dev rebuild. `eval` and `inspect` +additionally exit `1` for a failing or inconclusive trial or an invalid model +even when no error diagnostic was reported. ## Code families diff --git a/docs/effect-conventions.md b/docs/effect-conventions.md index 5ad5efd4b..0ae16a177 100644 --- a/docs/effect-conventions.md +++ b/docs/effect-conventions.md @@ -11,6 +11,7 @@ docs, or examples' user code. The four-concept newcomer ledger is untouched. | --- | --- | | npm `effect` | **`4.0.0-rc.112`** (exact). Latest published `rc` dist-tag on 2026-09-01. The Wave 3.5 brief named `4.0.0-rc.113`; that version was not on the registry. Re-pin chores take the next published RC. | | Vendored tree | `repos/effect` via `git subtree` from [Effect-TS/effect](https://github.com/Effect-TS/effect.git) `main` (v4). Squash commit tracks `packages/effect` version **4.0.0-rc.112**. | +| `website` `typescript` | **`6.0.3`**, behind the root's TypeScript 7, because `typedoc@0.28` peers on `<= 6.0.x`. TypeDoc and twoslash compile `packages/agent-bundle/src` with it, so TS7-only syntax in the package breaks `pnpm docs:site:build` first. Re-pin chores check whether a newer `typedoc` lifts the ceiling. | Application code imports the npm package. Never import from `repos/**`. diff --git a/docs/superpowers/plans/2026-09-02-agent-bundle-rspress-docsite.md b/docs/superpowers/plans/2026-09-02-agent-bundle-rspress-docsite.md new file mode 100644 index 000000000..e058ba8c2 --- /dev/null +++ b/docs/superpowers/plans/2026-09-02-agent-bundle-rspress-docsite.md @@ -0,0 +1,818 @@ +# Agent Bundle Rspress Docsite Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build and deploy a bilingual Rspress documentation site for agent-bundle with generated TypeScript API docs, type-aware examples, built-in validation, and AI-readable outputs. + +**Architecture:** A private `website` pnpm workspace owns Rspress configuration, theme code, English and Chinese authored docs, and shared generated TypeDoc pages. The repository exposes isolated docs scripts, while a dedicated GitHub Pages workflow validates pull requests and deploys `website/doc_build` only from `main`. + +**Tech Stack:** Rspress 2.0.21, React 19.2.8, TypeScript 6.0.3, `@rspress/plugin-llms`, `@rspress/plugin-twoslash`, `@rspress/plugin-typedoc`, `@rspress/plugin-sitemap`, pnpm 11.23.0, GitHub Pages Actions. + +## Global Constraints + +- Node.js remains `>=22.19.0`; CI uses Node 22.19.0. +- The production URL is `https://scriptedalchemy.github.io/agent-bundle/`. +- The Rspress `siteOrigin` is `https://scriptedalchemy.github.io` and `base` is `/agent-bundle/`. +- Hand-authored documentation has complete English and Simplified Chinese route parity. +- Shared generated TypeDoc pages document every public package export entry module. +- Use built-in local search; do not add Algolia. +- Enable dead-link, dead-anchor, dead-image, and language-parity checks. +- Enable locale-aware `llms.txt`, `llms-full.txt`, route Markdown, Twoslash, and sitemap generation. +- Keep docs builds separate from the npm package-release `build` command. +- Links from website pages to repository files outside the Rspress docs root use absolute `https://github.com/ScriptedAlchemy/agent-bundle/blob/main/...` URLs; relative links are reserved for intra-site navigation. +- Do not edit `repos/**`. +- Do not create commits unless the user explicitly requests them. +- Delegate straightforward scaffolding and mechanical work to Grok 4.6, difficult plugin/content/i18n work to Claude Opus 5, and reserve final integration and polish for GPT-5.6 Sol. + +--- + +## File Map + +### Workspace and build contract + +- Modify `package.json`: add root docs scripts only. +- Modify `pnpm-workspace.yaml`: include `website`. +- Modify `pnpm-lock.yaml`: record the new workspace and dependencies through pnpm. +- Modify `.gitignore`: ignore Rspress output and generated TypeDoc Markdown. +- Create `website/package.json`: private docs package and scripts. +- Create `website/tsconfig.json`: isolated TypeScript 6 config for Rspress and theme code. +- Create `website/scripts/verify-build.mjs`: verify required static artifacts. + +### Rspress application + +- Create `website/rspress.config.ts`: site, locale, validation, plugin, search, and Pages configuration. +- Create `website/typedoc.json`: exactly one key pointing TypeDoc at the package build tsconfig. +- Create `website/plugins/mirror-api-locale.ts`: copy generated English API Markdown into the Chinese locale after TypeDoc runs. +- Create `website/theme/index.tsx`: import the theme stylesheet and re-export Rspress's original theme. +- Create `website/theme/index.css`: bounded agent-bundle brand tokens and homepage styling. +- Create `website/docs/public/logo.svg`: local light/dark-compatible site mark. +- Create `website/docs/en/api/_meta.json`: committed English API sidebar order. +- Create `website/docs/zh/api/_meta.json`: committed Chinese API sidebar order with localized labels. + +### English and Chinese authored content + +- Create `website/docs/{en,zh}/index.md`. +- Create `website/docs/{en,zh}/_nav.json`. +- Create `website/docs/{en,zh}/guide/_meta.json`. +- Create `website/docs/{en,zh}/guide/start/{_meta.json,index.mdx,installation.mdx,quick-start.mdx,project-structure.mdx}`. +- Create `website/docs/{en,zh}/guide/authoring/{_meta.json,index.mdx,skills.mdx,hooks.mdx,mcp.mdx,scripts-assets.mdx,package-entries.mdx}`. +- Create `website/docs/{en,zh}/guide/development/{_meta.json,index.mdx,workbench.mdx,testing.mdx,evaluations.mdx}`. +- Create `website/docs/{en,zh}/guide/distribution/{_meta.json,index.mdx,validation.mdx,installation.mdx,preview-packages.mdx}`. +- Create `website/docs/{en,zh}/reference/{_meta.json,index.mdx,cli.mdx,configuration.mdx,targets-artifacts.mdx,runtime-environment.mdx,security.mdx,limitations.mdx,api.mdx}`. +- Create `website/docs/{en,zh}/examples/{_meta.json,index.mdx,skills-starter.mdx,hooks-and-scripts.mdx,mcp-app.mdx,audiobook-curator.mdx}`. +- Create `website/docs/{en,zh}/contributing/{_meta.json,index.mdx}`. + +### Delivery and discoverability + +- Create `.github/workflows/docs.yml`: validate PRs and deploy main to Pages. +- Modify `README.md`: link to the hosted docs near the introduction. +- Modify `packages/agent-bundle/README.md`: link to the hosted docs. +- Modify `packages/agent-bundle/package.json`: set the package homepage to the hosted docs. + +--- + +### Task 1: Scaffold the docs workspace and artifact contract + +**Owner/model:** Grok 4.6 — mechanical workspace and package setup. + +**Files:** +- Modify: `package.json` +- Modify: `pnpm-workspace.yaml` +- Modify: `pnpm-lock.yaml` +- Modify: `.gitignore` +- Create: `website/package.json` +- Create: `website/tsconfig.json` +- Create: `website/scripts/verify-build.mjs` + +**Interfaces:** +- Produces: root `docs:site:dev`, `docs:site:build`, and `docs:site:preview` commands. +- Produces: `@agent-bundle/docs` workspace with local TypeScript 6 despite the repository's root TypeScript 7. +- Produces: a persistent output assertion used after every production build. + +- [ ] **Step 1: Add the failing static-output verifier** + +Create `website/scripts/verify-build.mjs` with Node built-ins only. It must check these paths relative to `website/doc_build` and report every missing artifact in one failure: + +```js +import { access } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const websiteRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const outputRoot = path.join(websiteRoot, 'doc_build'); +const requiredArtifacts = [ + 'index.html', + 'zh/index.html', + 'api/index.html', + 'zh/api/index.html', + 'llms.txt', + 'llms-full.txt', + 'zh/llms.txt', + 'zh/llms-full.txt', + 'sitemap.xml', +]; + +const missing = []; +for (const artifact of requiredArtifacts) { + try { + await access(path.join(outputRoot, artifact)); + } catch { + missing.push(artifact); + } +} + +if (missing.length > 0) { + throw new Error(`Missing documentation build artifacts:\n- ${missing.join('\n- ')}`); +} + +console.log(`Verified ${requiredArtifacts.length} documentation build artifacts.`); +``` + +- [ ] **Step 2: Verify the output contract fails before the site exists** + +Run: + +```bash +node website/scripts/verify-build.mjs +``` + +Expected: non-zero exit with all nine required artifacts listed as missing. + +This script is the mandatory backstop for a known upstream behavior: +`@rspress/plugin-typedoc@2.0.21` calls `app.convert()` and simply skips output +when conversion fails, so a broken TypeDoc run otherwise produces a green +build with no API reference. Task 2 extends this script with one generated +module page per public package export. + +> **Deviation (Task 7):** the verifier was removed in favor of checks Rspress +> already runs. A TypeDoc run that produces no output fails `rspress build` +> during sidebar resolution, because the hand-authored +> `website/docs/{en,zh}/api/_meta.json` name the generated directories; a +> partial run fails the dead-link check, because the Reference pages link to +> specific generated module and interface pages, and the Reference overview +> links every generated reference page. Both were verified by breaking the +> TypeDoc entry points and building. + +- [ ] **Step 3: Create the private website package** + +Create `website/package.json` with: + +```json +{ + "name": "@agent-bundle/docs", + "version": "0.0.0", + "private": true, + "type": "module", + "engines": { + "node": ">=22.19.0" + }, + "scripts": { + "build": "rspress build", + "check": "pnpm typecheck && pnpm build && pnpm verify:build", + "dev": "rspress dev", + "preview": "rspress preview", + "typecheck": "tsc --project tsconfig.json", + "verify:build": "node scripts/verify-build.mjs" + }, + "devDependencies": { + "@rspress/core": "2.0.21", + "@rspress/plugin-llms": "2.0.21", + "@rspress/plugin-sitemap": "2.0.21", + "@rspress/plugin-twoslash": "2.0.21", + "@rspress/plugin-typedoc": "2.0.21", + "@types/node": "26.4.0", + "@types/react": "19.2.18", + "@types/react-dom": "19.2.5", + "agent-bundle": "workspace:*", + "react": "19.2.8", + "react-dom": "19.2.8", + "typescript": "6.0.3" + } +} +``` + +TypeScript 6.0.3 is package-local and intentional: `@rspress/plugin-twoslash@2.0.21` declares `typescript: ^6.0.3`, while the repository root currently uses TypeScript 7. + +- [ ] **Step 4: Create the isolated website TypeScript config** + +Create `website/tsconfig.json`: + +```json +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "isolatedModules": true, + "jsx": "react-jsx", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2024", + "types": ["node", "react"] + }, + "include": ["rspress.config.ts", "theme/**/*.ts", "theme/**/*.tsx"] +} +``` + +- [ ] **Step 5: Register the workspace and root scripts** + +Add `website` to `pnpm-workspace.yaml`. + +Add to root `package.json`: + +```json +{ + "scripts": { + "docs:site:build": "pnpm --filter @agent-bundle/docs check", + "docs:site:dev": "pnpm --filter @agent-bundle/docs dev", + "docs:site:preview": "pnpm --filter @agent-bundle/docs preview" + } +} +``` + +Do not modify the existing root `build` or `check` command composition. + +- [ ] **Step 6: Ignore generated outputs** + +Append: + +```gitignore +website/doc_build/ +website/docs/en/api/**/*.md +website/docs/zh/api/**/*.md +``` + +The `**/*.md` scoping is deliberate: the committed +`website/docs/en/api/_meta.json` and `website/docs/zh/api/_meta.json` files +must stay tracked. + +- [ ] **Step 7: Install and lock dependencies** + +Run: + +```bash +pnpm install +``` + +Expected: `pnpm-lock.yaml` records the `website` importer and all five Rspress packages at 2.0.21 without peer-dependency warnings. + +If installation is blocked by the workspace minimum-release-age policy for a +freshly published `@rspress/*` patch, add the exact blocked versions to +`minimumReleaseAgeExclude` in `pnpm-workspace.yaml` rather than downgrading. + +- [ ] **Step 8: Verify workspace command wiring** + +Run: + +```bash +pnpm --filter @agent-bundle/docs typecheck +``` + +Expected: fail because `website/rspress.config.ts` does not exist yet. This confirms the workspace resolves and the next task owns the missing application. + +--- + +### Task 2: Configure Rspress, plugins, shared API generation, and the lean theme + +**Owner/model:** Claude Opus 5 — plugin compatibility, i18n, and TypeDoc are the highest-risk configuration work. + +**Files:** +- Create: `website/rspress.config.ts` +- Create: `website/typedoc.json` +- Create: `website/plugins/mirror-api-locale.ts` +- Create: `website/theme/index.tsx` +- Create: `website/theme/index.css` +- Create: `website/docs/public/logo.svg` +- Create: `website/docs/en/api/_meta.json` +- Create: `website/docs/zh/api/_meta.json` +- Modify: `website/scripts/verify-build.mjs` +- Create: `website/docs/en/index.md` +- Create: `website/docs/zh/index.md` +- Create: `website/docs/en/_nav.json` +- Create: `website/docs/zh/_nav.json` +- Create: initial locale section `_meta.json` files from the File Map + +**Interfaces:** +- Consumes: the `@agent-bundle/docs` package and TypeScript 6 toolchain from Task 1. +- Produces: `rspress build` output under `website/doc_build`. +- Produces: shared `/api/` TypeDoc routes and locale-aware LLM outputs. + +- [ ] **Step 1: Add minimal paired locale routes and navigation metadata** + +Create English and Chinese homepages with `pageType: home`, matching hero actions, and matching feature-card links. Create `_nav.json` files with locale-appropriate labels and identical route targets for Guide, Reference, Examples, Contributing, and shared Type API. + +Create every section directory and `_meta.json` listed in the File Map before enabling language parity. Page-order arrays must name only pages that exist by the end of the owning content task. + +- [ ] **Step 2: Add the per-locale generated API sidebars** + +Rspress i18n requires every content directory to live under a locale root, so +the generated reference lives at `docs/en/api` (route `/api/`) and is mirrored +to `docs/zh/api` (route `/zh/api/`). Create `website/docs/en/api/_meta.json`: + +```json +[ + "index", + { + "type": "dir", + "name": "modules", + "label": "Modules" + }, + { + "type": "dir", + "name": "functions", + "label": "Functions" + }, + { + "type": "dir", + "name": "interfaces", + "label": "Interfaces" + }, + { + "type": "dir", + "name": "type-aliases", + "label": "Type aliases" + } +] +``` + +Create `website/docs/zh/api/_meta.json` with the same entries and localized +`label` values. `pluginTypeDoc` writes `_meta.json` only when absent, so both +committed files are stable — but that also means a newly added package export +never appears in the sidebar until both files are hand-updated; the verify +script's per-export assertion is what makes that failure loud. + +After the first TypeDoc run, reconcile both lists with directories TypeDoc actually generated; remove nonexistent directory entries rather than suppressing dead-link checks. + +- [ ] **Step 3: Configure Rspress and all approved plugins** + +Create `website/rspress.config.ts` with top-level imports only and: + +- `root: path.join(import.meta.dirname, 'docs')` +- `siteOrigin: 'https://scriptedalchemy.github.io'` +- `base: '/agent-bundle/'` +- English default locale and Simplified Chinese locale +- local logo and `logoText: 'agent-bundle'` +- `search: { codeBlocks: true }` +- `route: { cleanUrls: true, localeRedirect: 'never' }` +- `markdown.link.checkDeadLinks` excluding generated LLM text targets (dead-link and dead-image checks default on; the excludes list and `checkAnchors` are the real opt-ins) +- `markdown.link.checkAnchors: true` +- `markdown.image.checkDeadImages: true` +- `languageParity.enabled: true` (the published type is `enabled`; `enable` is silently ignored) +- `languageParity.include` covering `index.md`, `guide`, `reference`, `examples`, and `contributing` +- `languageParity.exclude: ['api']` — generated pages are identical by design and only `_meta.json` is tracked +- GitHub edit link and social link +- `themeConfig.llmsUI: { placement: 'outline' }` +- keep the core `llms` option at its default `false`; enabling it alongside `pluginLlms` double-generates + +Register plugins in generation order: + +1. `pluginTypeDoc` +2. the local `mirrorApiLocale` plugin +3. `pluginTwoslash` +4. `pluginLlms` +5. `pluginSitemap` + +TypeDoc entry points are exactly: + +```ts +const packageSource = path.join( + import.meta.dirname, + '..', + 'packages', + 'agent-bundle', + 'src', +); + +const publicApiEntryPoints = [ + 'index.ts', + 'api.ts', + 'cli-entry.ts', + 'config/index.ts', + 'eval/index.ts', + 'mcp-apps.ts', + 'meta.ts', + 'mcp-entry.ts', + 'rstest/index.ts', + 'test/index.ts', + 'test/browser.ts', +].map(entry => path.join(packageSource, entry)); +``` + +Configure `pluginTypeDoc` with `outDir: 'en/api'`. Do not include Rslib +entries that are absent from the package export map. + +Create `website/typedoc.json` containing exactly: + +```json +{ + "tsconfig": "../packages/agent-bundle/tsconfig.build.json" +} +``` + +This single key is load-bearing twice over. Without it, TypeDoc discovers +`website/tsconfig.json` from the working directory and compiles the package +sources under the wrong compiler settings. And it must stay a single key: +TypeDoc's config file wins over the plugin's inlined options, so any extra key +risks clobbering the plugin's markdown router settings. Verify explicitly +after the first build that markdown output is still generated with +`typedoc.json` present. + +Create `website/plugins/mirror-api-locale.ts`, an Rspress plugin registered +immediately after `pluginTypeDoc`. In its `config` hook (after TypeDoc has +written `docs/en/api`), it copies every generated `.md` file from +`docs/en/api` into `docs/zh/api`, skipping `_meta.json` so the localized +Chinese sidebar stays authoritative. It first deletes previously mirrored +`.md` files under `docs/zh/api` (and stale generated `.md` under +`docs/en/api` is removed before regeneration by TypeDoc's own run plus this +cleanup), so removed package exports cannot leave phantom local routes. +Mirroring is preferred over a second `pluginTypeDoc` instance because a second +instance would run the whole TypeScript program twice per build. + +Because generation runs in the `config` hook before route scanning, generated +pages exist before dead-link checking, so authored pages may safely link into +`/api/…` even though nothing generated is committed. The same placement means +every `rspress dev` startup pays a full TypeScript program build over the +package source graph; if that becomes painful for authoring, an optional +`DOCS_SKIP_API=1` gate around `pluginTypeDoc` plus the mirror is acceptable — +dev-only, never in `docs:site:build`. Generated API pages will also render an +"Edit this page" link pointing at generated paths that do not exist in the +repository; accept that knowingly for this iteration rather than patching the +theme. + +Configure Twoslash as `pluginTwoslash()` with defaults (`explicitTrigger` +already defaults to `true`) plus source-mapped resolution so examples import +real public APIs without a prebuilt `dist`: + +```ts +pluginTwoslash({ + twoslashOptions: { + compilerOptions: { + paths: { + 'agent-bundle': [path.join(packageSource, 'index.ts')], + 'agent-bundle/config': [path.join(packageSource, 'config/index.ts')], + 'agent-bundle/test': [path.join(packageSource, 'test/index.ts')], + 'agent-bundle/eval': [path.join(packageSource, 'eval/index.ts')], + }, + }, + }, +}) +``` + +Do not add the package-release build to `docs:site:build`. + +Use `pluginLlms` explicitly with its locale defaults (English root files, +Chinese files under `zh/`), excluding the generated reference so eleven entry +modules of API text do not dominate `llms-full.txt`: + +```ts +pluginLlms({ + exclude: ({ page }) => page.routePath.includes('/api/'), +}) +``` + +Use `pluginSitemap()` without duplicating the site URL because +`siteOrigin + base` already provides it. + +- [ ] **Step 4: Add the lean theme and local logo** + +`website/theme/index.tsx` contains only: + +```tsx +import './index.css'; + +export * from '@rspress/core/theme-original'; +``` + +The stylesheet defines agent-bundle brand variables for light and dark modes, +keeps readable contrast, and applies bounded homepage accent styling. The SVG +logo must use current color or provide light/dark-safe fills; it must not reuse +test-fixture artwork. + +- [ ] **Step 5: Extend the verify script with per-export API assertions** + +After observing the generated layout under `website/docs/en/api`, extend +`website/scripts/verify-build.mjs` so it asserts one generated module page in +`doc_build` for each of the eleven public export entry modules, in both +`api/` and `zh/api/`. This is the hard gate against `pluginTypeDoc`'s +silent-skip behavior and against sidebar drift when exports change. + +- [ ] **Step 6: Typecheck and build** + +Run: + +```bash +pnpm --filter @agent-bundle/docs typecheck +pnpm --filter @agent-bundle/docs build +pnpm --filter @agent-bundle/docs verify:build +``` + +Expected: all pass, with the typecheck resolving the workspace-local +TypeScript 6.0.3 rather than the root TypeScript 7. Inspect +`website/docs/en/api` on disk and confirm TypeDoc actually produced module +pages. If TypeDoc's generated directory names differ from the curated API +`_meta.json` files, reconcile metadata and rerun; never weaken dead-link +checking. The likeliest real failure here is the package source graph +(including the `effect` 4 RC type surface) type-checking differently under +TypeScript 6 than under the repo's TypeScript 7 — if that happens, fix by +narrowing TypeDoc entry points or adjusting `typedoc.json`'s tsconfig target, +and surface the divergence rather than masking it. + +--- + +### Task 3: Migrate bilingual Start and Authoring documentation + +**Owner/model:** Claude Opus 5 — preserve technical meaning while producing complete Chinese parity. + +**Files:** +- Create/modify: `website/docs/{en,zh}/guide/_meta.json` +- Create: all `website/docs/{en,zh}/guide/start/*` files from the File Map +- Create: all `website/docs/{en,zh}/guide/authoring/*` files from the File Map + +Links to repository files outside the docs root (for example +`docs/entry-conventions.md`) must use absolute GitHub URLs — relative +filesystem links to them have no route and fail `checkDeadLinks`. + +**Source material:** +- `README.md:1-76` +- `packages/agent-bundle/README.md:1-79` +- `docs/framework-mode.md` +- `docs/entry-conventions.md` + +**Content contract:** +- Introduction explains one typed config compiled to Claude Code, Codex, Cursor, and portable artifacts. +- Installation clearly distinguishes pkg.pr.new preview installation from future npm commands. +- Quick start includes scaffolder and manual `defineConfig` paths. +- Project structure explains config, Skills, hooks, MCP, scripts, assets, routes, and output. +- Authoring pages cover the exact README contracts for Skills, hooks, MCP servers/apps, assets, scripts, `bin`, `lib`, framework stdio lifecycle, and the single bundler escape hatch. +- Every runnable TypeScript example that imports a public package entry uses an explicit `twoslash` code fence and resolves under the docs build. +- Chinese pages translate explanations while preserving commands, identifiers, paths, JSON keys, and host product names exactly. + +- [ ] **Step 1: Write each English/Chinese page pair together** + +Never land an English route without its Chinese counterpart. Keep heading +structure aligned so cross-locale anchors remain predictable. + +- [ ] **Step 2: Update page-order metadata only after files exist** + +Use filename arrays inside `start/_meta.json` and `authoring/_meta.json`. +Use `dir-section-header` records in `guide/_meta.json`. Task 2 shipped +`_nav.json` with only the Type API entry because no other routes existed; +this task must add the Guide entry to both locale `_nav.json` files — nav +links are not dead-link-checked, so nothing else will catch the omission. + +- [ ] **Step 3: Validate the section** + +Run: + +```bash +pnpm docs:site:build +``` + +Expected: pass with Twoslash, links, anchors, images, and locale parity enabled. + +--- + +### Task 4: Migrate bilingual Development, Distribution, and Reference documentation + +**Owner/model:** Claude Opus 5 — this section contains the densest runtime, evidence, and security contracts. + +**Files:** +- Create: all `website/docs/{en,zh}/guide/development/*` files from the File Map +- Create: all `website/docs/{en,zh}/guide/distribution/*` files from the File Map +- Create: all `website/docs/{en,zh}/reference/*` files from the File Map + +**Source material:** +- `packages/agent-bundle/README.md:65-438` +- `docs/entry-conventions.md` +- `docs/framework-mode.md` +- `docs/preview-packages.md` +- `docs/diagnostics.md` + +**Content contract:** +- Workbench documentation preserves loopback-only, foreground, epoch, MCP session, Playground trace, and Agent API boundaries. +- Testing documentation keeps route-unit, mcp-in-memory, cli-dispatch, packed-stdio, packed-deleted-source, and host-install proof levels distinct. +- Evaluation documentation preserves pass/fail/inconclusive semantics and authenticated native harness constraints. +- Distribution documentation covers build, artifact validation, strict Claude validation, generated installation docs, and host-specific install behavior. +- Reference pages capture CLI purpose, config semantics, target layouts, `AGENT_BUNDLE_PLUGIN_ROOT`, runtime floors, credential refusal, and current limitations. +- Both locale API landing pages link to the shared generated `/api/` reference and explain that source symbol comments remain in their authored language. + +- [ ] **Step 1: Write paired Development pages and validate** + +Run `pnpm docs:site:build`; expected pass. + +- [ ] **Step 2: Write paired Distribution pages and validate** + +Run `pnpm docs:site:build`; expected pass. + +- [ ] **Step 3: Write paired Reference pages and validate** + +Add the Reference entry to both locale `_nav.json` files (nav links are not +dead-link-checked). Run `pnpm docs:site:build`; expected pass. + +--- + +### Task 5: Finish bilingual Examples, Contributing, and homepage polish + +**Owner/model:** Claude Opus 5 for content accuracy; Grok 4.6 may perform metadata-only ordering after content exists. + +**Files:** +- Create: all `website/docs/{en,zh}/examples/*` files from the File Map +- Create: all `website/docs/{en,zh}/contributing/*` files from the File Map +- Modify: `website/docs/{en,zh}/index.md` +- Modify: locale `_nav.json` and section `_meta.json` + +**Source material:** +- `README.md:77-99` +- `examples/skills-starter/README.md` +- `examples/hooks-and-scripts/README.md` +- `examples/mcp-app/README.md` +- `examples/audiobook-curator/README.md` +- `docs/local-ci.md` + +**Content contract:** +- Each example page states what it proves, its public package dependencies, its root run command, and its source link. +- Contributing pages document `pnpm check`, `pnpm check:release`, `pnpm check:local-ci`, Changesets, and the native-smoke boundary without presenting internal workflows as end-user requirements. +- Homepages use matching actions and feature links, polished English/Chinese copy, and no untranslated labels. + +- [ ] **Step 1: Write paired example pages** + +- [ ] **Step 2: Write paired contributing pages** + +- [ ] **Step 3: Finalize both homepages and navigation metadata** + +Family-parity requirements (rspress.rs / rslib.rs / rsbuild.rs conventions): +hero actions become Introduction (brand) and Quick start (alt) into the Guide, +GitHub moves out of the hero (social link already covers it); every feature +card gains a `link`; both locale `_nav.json` files carry Guide, Reference, +Examples, Contributing, and Type API entries. + +- [ ] **Step 4: Run the complete docs gate** + +Run: + +```bash +pnpm docs:site:build +``` + +Expected: pass and verify all nine required build artifacts plus the +per-export API assertions. + +--- + +### Task 6: Add GitHub Pages delivery and documentation entry links + +**Owner/model:** Grok 4.6 — workflow and link updates are bounded, mechanical changes. + +**Files:** +- Create: `.github/workflows/docs.yml` +- Modify: `scripts/classify-docs-only.mjs` +- Modify: `packages/agent-bundle/tests/classify-docs-only.test.ts` +- Modify: `README.md` +- Modify: `packages/agent-bundle/README.md` +- Modify: `packages/agent-bundle/package.json` + +**Interfaces:** +- Consumes: `pnpm docs:site:build` and `website/doc_build`. +- Produces: PR validation and main-only Pages deployment. + +- [ ] **Step 1: Add the Pages workflow** + +Use: + +- `actions/checkout@v7` +- `pnpm/setup@v2` with cache, `install: false`, and `runtime: node@22.19.0` +- `actions/configure-pages@v6` +- `actions/upload-pages-artifact@v5` +- `actions/deploy-pages@v5` + +Run the workflow unconditionally on every pull request, every push to `main`, +and `workflow_dispatch` — no `paths:` filters. Path filtering would fail in +both directions: a `packages/agent-bundle/src/**` change can break TypeDoc +generation without touching `website/**`, and a path-filtered workflow can +never safely become a required status check. + +The build job runs: + +```bash +pnpm install --frozen-lockfile +pnpm docs:site:build +``` + +Upload the Pages artifact only for pushes to `main`. A dependent deploy job +runs only for `push` on `refs/heads/main`, uses the `github-pages` +environment with `url: ${{ steps.deployment.outputs.page_url }}`, and runs +configure/deploy Pages actions. + +Permissions and concurrency: + +- top level: `contents: read` (checkout needs it) +- deploy job additionally: `pages: write`, `id-token: write` +- build job concurrency: PR-keyed group with `cancel-in-progress` for pull + requests, matching `ci.yml`'s existing pattern +- deploy job concurrency: `group: pages` with `cancel-in-progress: false` — + never cancel an in-flight Pages deployment + +Record one manual prerequisite in the workflow header comment: repository +Settings → Pages must be set to the "GitHub Actions" source before the first +deploy, or `deploy-pages` fails. + +- [ ] **Step 2: Classify website-only PRs as docs-only in CI** + +Add `website/` to the docs-only allowlist in `scripts/classify-docs-only.mjs` +so website-only PRs skip the heavy `verify`/`examples-check`/`release-gates` +jobs, which `docs.yml` now independently covers. Update +`packages/agent-bundle/tests/classify-docs-only.test.ts` in the same change — +the classifier is unit-tested and must stay green: + +```bash +pnpm exec rstest --config rstest.unit.config.ts packages/agent-bundle/tests/classify-docs-only.test.ts +``` + +Expected: pass with new cases asserting `website/**` is docs-only and that +mixed website+source PRs remain non-docs-only. + +- [ ] **Step 3: Link repository and package readers to the site** + +Add `https://scriptedalchemy.github.io/agent-bundle/` near the introductions +of both READMEs. Set `packages/agent-bundle/package.json` `homepage` to that +URL. Do not remove useful install or command information from either README. +While editing the root README, drop the deprecated `version` field from its +quick-start `defineConfig` example (`plugin.version` is `@deprecated` in +source and the website docs intentionally omit it). + +- [ ] **Step 4: Validate workflow syntax and docs** + +Run: + +```bash +pnpm docs:site:build +pnpm lint +``` + +Expected: both pass. + +--- + +### Task 7: Final integration, browser acceptance, and polish + +**Owner/model:** GPT-5.6 Sol — resolve integration failures, audit generated output, and perform final polish. + +**Files:** +- Modify only files introduced or intentionally changed by Tasks 1-6. + +- [ ] **Step 1: Inspect the full working-tree diff** + +Confirm no files under `repos/**` changed, no generated TypeDoc Markdown or +`doc_build` output is tracked, and package-release scripts are unchanged. + +- [ ] **Step 2: Run fresh repository-level verification** + +Run: + +```bash +pnpm install --frozen-lockfile +pnpm docs:site:build +pnpm lint +``` + +Expected: all pass with no peer warnings or Rspress validation warnings. + +- [ ] **Step 3: Audit generated outputs** + +Verify: + +- English and Chinese LLM indexes contain the expected localized route groups. +- LLM links include `https://scriptedalchemy.github.io/agent-bundle/`. +- `sitemap.xml` uses the same origin and base. +- TypeDoc's API index represents all eleven package export entry modules. +- no generated page contains a source-machine absolute path. + +- [ ] **Step 4: Run desktop browser acceptance at 1440×900** + +Start `pnpm docs:site:dev` once, then verify: + +1. English and Chinese homepages render with no loading state or missing asset. +2. Locale switching preserves the corresponding route. +3. Top navigation and section sidebars reach every authored section. +4. Built-in search returns prose and code-block matches without Algolia. +5. A Twoslash-enabled TypeScript example exposes type information. +6. The shared TypeDoc API reference is reachable from both locale API pages. +7. LLM actions resolve to generated Markdown under `/agent-bundle/`. + +- [ ] **Step 5: Check diagnostics for edited TypeScript and TSX files** + +Read editor diagnostics for: + +- `website/rspress.config.ts` +- `website/theme/index.tsx` + +Fix only diagnostics introduced by this work. + +- [ ] **Step 6: Report completion without merging** + +Summarize created structure, plugin decisions, content coverage, deployment +workflow, and exact verification evidence. Task work is committed on the +isolated `docs/rspress-website` branch as part of the subagent workflow; +leave the branch unmerged and open no PR unless the user separately asks. diff --git a/docs/superpowers/specs/2026-09-02-agent-bundle-rspress-docsite-design.md b/docs/superpowers/specs/2026-09-02-agent-bundle-rspress-docsite-design.md new file mode 100644 index 000000000..907287764 --- /dev/null +++ b/docs/superpowers/specs/2026-09-02-agent-bundle-rspress-docsite-design.md @@ -0,0 +1,311 @@ +# Agent Bundle Rspress Documentation Site Design + +## Goal + +Add a production-ready Rspress documentation website for agent-bundle that +turns the existing repository and package READMEs into navigable English and +Chinese documentation, publishes to GitHub Pages, exposes the public +TypeScript API, and emits AI-readable documentation artifacts. + +## Upstream Conventions + +The site follows the current Rspress and Rslib repository conventions: + +- A private `website` pnpm workspace owns the documentation application. +- `website/rspress.config.ts` contains site, build, plugin, and theme + configuration. +- Authored content lives below `website/docs`. +- Locale roots use `_nav.json` for top-level navigation. +- Section directories use `_meta.json` for sidebar grouping and ordering. +- The theme re-exports the Rspress original theme and adds only bounded + product-specific presentation. +- `rspress dev`, `rspress build`, and `rspress preview` remain the primary + package commands. + +The design deliberately does not copy upstream analytics, hosted Algolia +search, component preview infrastructure, or blog feeds. Agent-bundle does +not currently need those services. + +## Workspace Architecture + +Create a private workspace at `website/` named `@agent-bundle/docs`. +`pnpm-workspace.yaml` will include `website`, and the repository root will +expose: + +- `docs:site:dev` to run the Rspress development server. +- `docs:site:build` to typecheck the website and perform the production build + (the check gate; there is no separate build-only root script). +- `docs:site:preview` to preview the production output. + +The documentation build remains separate from the package-release `build` +script. Publishing npm packages must not become dependent on documentation +generation or GitHub Pages. + +The website package contains: + +- `rspress.config.ts` for the site contract. +- `tsconfig.json` for config and theme typechecking. +- `theme/index.tsx` and a small stylesheet for agent-bundle branding. +- `docs/en` and `docs/zh` for hand-authored localized content. +- `docs/en/api` for TypeDoc-generated public API documentation, mirrored into + `docs/zh/api` by a small local plugin (Rspress i18n requires all content to + live under a locale root). +- `docs/public` for logos and other static assets. + +The theme remains close to Rspress defaults. It may adjust brand colors, +homepage presentation, and the navigation mark, but it does not replace core +layout, search, sidebar, outline, or accessibility behavior. + +## Site and Route Configuration + +The production site is hosted at: + +`https://scriptedalchemy.github.io/agent-bundle/` + +Rspress configuration uses: + +- `siteOrigin: "https://scriptedalchemy.github.io"` +- `base: "/agent-bundle/"` +- `root` pointing to `website/docs` +- English as the default language +- English and Simplified Chinese locale metadata +- clean URLs +- built-in local search with code-block indexing +- edit links targeting the corresponding source under `website/docs` + +The configuration enables Rspress's built-in checks for: + +- dead internal links +- dead internal anchors +- missing local images +- English and Chinese route parity + +Relative source-file links are preferred inside Markdown and MDX so links +remain useful in editors and GitHub and are independent of the deployment +base path. + +## Plugin Selection + +The website installs and enables: + +### `@rspress/plugin-llms` + +Generate: + +- root English `llms.txt` +- root English `llms-full.txt` +- `zh/llms.txt` +- `zh/llms-full.txt` +- Markdown representations for documentation routes + +The default theme's LLM UI is enabled in the page outline so readers can copy +or open the Markdown representation of a page. + +### `@rspress/plugin-twoslash` + +Opted-in TypeScript examples use Twoslash for IDE-style type hovers, inferred +type queries, completions, and diagnostics. Explicit triggering remains +enabled so ordinary code blocks do not incur unnecessary type analysis or +fail because they intentionally show incomplete fragments. + +### `@rspress/plugin-typedoc` + +Generate browsable API documentation from every public `agent-bundle` +TypeScript entry module represented by the package export map. The generated +reference includes modules, functions, interfaces, parameters, return types, +and source API comments. + +TypeDoc content is generated once and mirrored across locales because symbols, +signatures, and source comments are one package-level contract. Hand-authored +API orientation pages in both locales link into that generated reference. + +Generated TypeDoc Markdown is ignored by Git in both locale API directories. +Curated per-locale API `_meta.json` files are source-controlled so navigation +order and localized section labels are stable. Because the TypeDoc plugin +skips output silently when conversion fails, a post-build verification script +asserts the generated API pages exist; that script, not the plugin, is the +build-failure guarantee for API generation. + +### `@rspress/plugin-sitemap` + +Generate `sitemap.xml` from `siteOrigin` and `base` for the GitHub Pages site. + +### Excluded Plugins + +- Algolia is excluded because built-in search meets the initial requirements + without external credentials or crawler infrastructure. +- API Docgen is excluded because TypeDoc better represents a multi-entry + TypeScript library; API Docgen primarily serves React prop tables or + isolated utility JSDoc. +- Preview and Playground are excluded because agent-bundle is not a component + library and its Workbench cannot run meaningfully as an isolated browser + code block. +- RSS is excluded because the initial site has no blog or release feed. +- Client Redirects is excluded because the new site has no legacy routes. + +## Documentation Information Architecture + +Both locales have the same hand-authored route structure. + +### Home + +The homepage presents the product, installation and quick-start actions, and +feature cards for: + +- one typed configuration across supported hosts +- Skills, hooks, MCP servers/apps, scripts, and package entries +- local Workbench development +- evidence-driven route, protocol, CLI, package, and host testing + +### Guide + +`Start` contains: + +- Introduction +- Installation +- Quick start +- Project structure + +`Authoring` contains: + +- Configuration model +- Skills +- Hooks +- MCP servers and MCP Apps +- Scripts and assets +- CLI and library package entries + +`Development` contains: + +- Developer Workbench +- Route testing +- MCP and CLI proof levels +- Evaluations + +`Distribution` contains: + +- Building artifacts +- Artifact validation +- Installing into Claude Code, Codex, and Cursor +- Preview packages + +### Reference + +Reference pages cover: + +- CLI commands and flags +- configuration semantics +- targets and artifact layouts +- runtime and environment contracts +- authentication and security constraints +- current limitations +- generated TypeDoc API entry point + +### Examples + +The examples section contains an overview and focused pages for: + +- Skills Starter +- Hooks and Scripts +- MCP App +- Audiobook Curator + +Each page explains the product behavior demonstrated by the example and links +to its source and local run command. + +### Contributing + +Contributor documentation covers: + +- repository development workflow +- local checks +- release gates +- links to deeper repository architecture documents + +## Content Migration Boundary + +The English documentation is assembled from the current root README and +`packages/agent-bundle/README.md`, split by user task rather than copied as +two long pages. Existing focused files under repository `docs/` remain the +source for contributor-level contracts when they are too detailed for the +initial user guide; the website links to them where appropriate. + +Chinese pages provide complete translations of every hand-authored English +route. They are not empty mirrors or placeholder pages. + +The root README remains a concise repository landing page. The package README +remains useful as the npm package landing page. Both should point readers to +the hosted documentation, while the website becomes the canonical navigable +long-form guide. + +## Build and Failure Behavior + +The documentation package exposes `dev`, `build`, `preview`, and `typecheck`. +The repository-level `docs:site:build` runs typechecking and then a production +Rspress build. + +The production build is the validation boundary. It fails on: + +- invalid Rspress configuration +- TypeScript errors in config or theme code +- invalid Markdown or MDX +- dead internal links +- dead internal anchors +- missing local images +- missing English or Chinese hand-authored route counterparts +- TypeDoc generation failures +- static-site rendering failures + +A successful build produces the static site, generated TypeDoc reference, +English and Chinese LLM files, per-route Markdown, local search index, and +sitemap. + +## GitHub Pages Delivery + +Add `.github/workflows/docs.yml`. + +The workflow: + +- runs unconditionally for all pull requests and pushes to `main` (no path + filters, because package-source changes can break TypeDoc generation and + path-filtered workflows cannot be required checks) +- supports manual dispatch +- installs the repository-pinned Node and pnpm versions +- installs dependencies with the frozen lockfile +- runs `pnpm docs:site:build` +- uploads `website/doc_build` as the Pages artifact +- deploys only for a push to `main` +- uses the official Pages configure, upload, and deploy actions +- grants `contents: read` at the top level plus Pages write and OIDC + permissions on the deploy job only +- cancels superseded pull-request builds, but never cancels an in-flight + Pages deployment (`group: pages`, `cancel-in-progress: false`) +- requires the repository's Pages source to be set to "GitHub Actions" once, + manually, before the first deployment + +Pull requests receive the complete typecheck and build validation without +deploying. + +## Acceptance + +Implementation is accepted when: + +1. A frozen-lockfile install succeeds. +2. `pnpm docs:site:build` succeeds with link, anchor, image, and language-parity + checks enabled. +3. The output contains the English and Chinese homepages. +4. The output contains English and Chinese `llms.txt` and `llms-full.txt` + artifacts. +5. The output contains `sitemap.xml`. +6. The shared generated TypeDoc reference exposes all package export entry + modules. +7. At a 1440 by 900 desktop viewport, both locale homepages render without a + loading state or broken assets. +8. Locale navigation, sidebars, built-in search, and language switching work. +9. A Twoslash-enabled example exposes type information. +10. The TypeDoc reference is reachable from both locales. +11. LLM page actions resolve to generated Markdown artifacts under the + `/agent-bundle/` base path. + +Mobile-specific layout and acceptance are outside this repository's desktop +application scope. diff --git a/package.json b/package.json index 9213e3dd0..2439f77bd 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,9 @@ "check": "pnpm build && pnpm test:unit && pnpm test:route-unit && pnpm test:projection && pnpm test:integration:run && pnpm lint && pnpm typecheck", "check:local-ci": "node scripts/local-ci.mjs", "check:host-cli": "node scripts/host-cli-pins.mjs verify", + "docs:site:build": "pnpm --filter @agent-bundle/docs check", + "docs:site:dev": "pnpm --filter @agent-bundle/docs dev", + "docs:site:preview": "pnpm --filter @agent-bundle/docs preview", "docs:runtime-topology": "node scripts/rsc-runtime-topology.mjs --root . --output docs/architecture/rsc-runtime-workbench.md", "eval:spot": "pnpm build && pnpm --filter @agent-bundle/rsc-agent-runtime-demo build && pnpm --filter @agent-bundle/rsc-agent-runtime-demo exec rstest run tests/micro-eval.spot.test.ts --config rstest.config.ts", "check:runtime-topology": "node scripts/rsc-runtime-topology.mjs --root . --output docs/architecture/rsc-runtime-workbench.md --check", diff --git a/packages/agent-bundle/README.md b/packages/agent-bundle/README.md index eab65b7fc..ac689e173 100644 --- a/packages/agent-bundle/README.md +++ b/packages/agent-bundle/README.md @@ -2,6 +2,8 @@ Compile a typed Agent Bundle configuration into portable, Codex, Claude Code, and Cursor artifacts. Node.js 22.19 or later is required. +Full documentation: [scriptedalchemy.github.io/agent-bundle](https://scriptedalchemy.github.io/agent-bundle/). + ```sh npm install --save-dev agent-bundle agent-bundle dev --root . diff --git a/packages/agent-bundle/src/adapters/capabilities/cursor-2026-08-28.json b/packages/agent-bundle/src/adapters/capabilities/cursor-2026-08-28.json index b29b1fdd0..d6542c6ca 100644 --- a/packages/agent-bundle/src/adapters/capabilities/cursor-2026-08-28.json +++ b/packages/agent-bundle/src/adapters/capabilities/cursor-2026-08-28.json @@ -469,6 +469,7 @@ "schemaOnlyFields": ["displayName", "publisher", "category", "tags", "minClientVersions"], "state": "supported" }, + "marketplace": ".cursor-plugin/marketplace.json", "marketplaceManifest": { "entryFields": ["name", "source", "description", "version", "author", "homepage", "repository", "license", "keywords", "logo", "category", "tags", "skills", "rules", "agents", "commands", "hooks", "mcpServers", "variables"], "evidence": [ diff --git a/packages/agent-bundle/src/adapters/capabilities/portable-1.0.0.json b/packages/agent-bundle/src/adapters/capabilities/portable-1.0.0.json index 13c44ea3a..6ab47814c 100644 --- a/packages/agent-bundle/src/adapters/capabilities/portable-1.0.0.json +++ b/packages/agent-bundle/src/adapters/capabilities/portable-1.0.0.json @@ -173,6 +173,11 @@ }, "skills": true }, + "tokens": { + "pluginData": "${PLUGIN_DATA}", + "pluginRoot": "${PLUGIN_ROOT}", + "workspaceRoot": false + }, "specificationSections": { "clientExtensions": "8", "componentDiscovery": "6", diff --git a/packages/agent-bundle/tests/classify-docs-only.test.ts b/packages/agent-bundle/tests/classify-docs-only.test.ts index e572bd69c..08ff35947 100644 --- a/packages/agent-bundle/tests/classify-docs-only.test.ts +++ b/packages/agent-bundle/tests/classify-docs-only.test.ts @@ -25,6 +25,10 @@ const docsOnlyPaths = [ '.changeset/nested/still-markdown.md', 'README.md', 'AGENTS.md', + 'website/docs/en/guide/start/index.mdx', + 'website/docs/zh/reference/_meta.json', + 'website/rspress.config.ts', + 'website/package.json', ] as const; const codePaths = [ @@ -32,7 +36,9 @@ const codePaths = [ 'examples/skills-starter/skills/release-review/SKILL.md', '.changeset/config.json', 'docs', + 'website', '.github/workflows/ci.yml', + '.github/workflows/docs.yml', 'package.json', 'scripts/classify-docs-only.mjs', ] as const; @@ -95,6 +101,24 @@ it('classifies mixed, nested-markdown, and rename pairs from a GitHub files list listingOk: true, })).toMatchObject({ docsOnly: false, reason: 'non-docs-path', path: 'packages/agent-bundle/README.md' }); + expect(classifyDocsOnlyListing({ + changedFilesCount: '2', + entries: parseGhFilesListing([ + 'website/docs/en/guide/authoring/hooks.mdx\t', + 'website/docs/zh/guide/authoring/hooks.mdx\t', + ].join('\n')), + listingOk: true, + })).toMatchObject({ docsOnly: true, reason: 'docs-only' }); + + expect(classifyDocsOnlyListing({ + changedFilesCount: '2', + entries: parseGhFilesListing([ + 'website/docs/en/reference/configuration.mdx\t', + 'packages/agent-bundle/src/core/types.ts\t', + ].join('\n')), + listingOk: true, + })).toMatchObject({ docsOnly: false, reason: 'non-docs-path', path: 'packages/agent-bundle/src/core/types.ts' }); + expect(classifyDocsOnlyListing({ changedFilesCount: '1', entries: parseGhFilesListing('docs/moved.md\tdocs/old.md\n'), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd744bc4b..98d867ee6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -426,6 +426,54 @@ importers: specifier: 19.2.5 version: 19.2.5(@types/react@19.2.18) + website: + devDependencies: + '@mdx-js/react': + specifier: 3.1.1 + version: 3.1.1(@types/react@19.2.18)(react@19.2.8) + '@rspress/core': + specifier: 2.0.21 + version: 2.0.21(@rspack/core@2.2.1(@swc/helpers@0.5.23))(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(supports-color@7.2.0) + '@rspress/plugin-llms': + specifier: 2.0.21 + version: 2.0.21(@rspress/core@2.0.21(@rspack/core@2.2.1(@swc/helpers@0.5.23))(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(supports-color@7.2.0))(supports-color@7.2.0) + '@rspress/plugin-sitemap': + specifier: 2.0.21 + version: 2.0.21(@rspress/core@2.0.21(@rspack/core@2.2.1(@swc/helpers@0.5.23))(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(supports-color@7.2.0)) + '@rspress/plugin-twoslash': + specifier: 2.0.21 + version: 2.0.21(@rspress/core@2.0.21(@rspack/core@2.2.1(@swc/helpers@0.5.23))(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(supports-color@7.2.0))(react@19.2.8)(supports-color@7.2.0)(typescript@6.0.3) + '@rspress/plugin-typedoc': + specifier: 2.0.21 + version: 2.0.21(@rspress/core@2.0.21(@rspack/core@2.2.1(@swc/helpers@0.5.23))(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(supports-color@7.2.0))(typescript@6.0.3) + '@shikijs/transformers': + specifier: 4.4.3 + version: 4.4.3 + '@types/node': + specifier: 26.4.0 + version: 26.4.0 + '@types/react': + specifier: 19.2.18 + version: 19.2.18 + '@types/react-dom': + specifier: 19.2.5 + version: 19.2.5(@types/react@19.2.18) + agent-bundle: + specifier: workspace:* + version: link:../packages/agent-bundle + react: + specifier: 19.2.8 + version: 19.2.8 + react-dom: + specifier: 19.2.8 + version: 19.2.8(react@19.2.8) + typescript: + specifier: 6.0.3 + version: 6.0.3 + zod: + specifier: 4.5.4 + version: 4.5.4 + packages: '@andrewbranch/untar.js@1.0.4': @@ -637,6 +685,18 @@ packages: '@emnapi/wasi-threads@1.2.3': resolution: {integrity: sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==} + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} + + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} + + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} + + '@gerrit0/mini-shiki@3.23.0': + resolution: {integrity: sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==} + '@hono/node-server@1.19.17': resolution: {integrity: sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==} engines: {node: '>=18.14.1'} @@ -674,6 +734,15 @@ packages: resolution: {integrity: sha512-6QEf6yqFbETdwGITKq57aYoPfX/3K8XFNwsAlx0C1M7o8cb79sv1M3w+tWuWvIcSbNqrLF7OD7YpZMVVz335hQ==} engines: {node: '>=20.0.0'} + '@mdx-js/mdx@3.1.1': + resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + + '@mdx-js/react@3.1.1': + resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==} + peerDependencies: + '@types/react': '>=16' + react: '>=16' + '@modelcontextprotocol/client@2.0.0': resolution: {integrity: sha512-8f1OghQ2rjzIOfqgUCP+8GiUWqRs89njoWLNqAe8kWmDePv3s1fZXseej+QXemssEuuOvLLmLO/kqM3IQHtISw==} engines: {node: '>=20'} @@ -1128,6 +1197,40 @@ packages: '@rspack/core': optional: true + '@rspress/core@2.0.21': + resolution: {integrity: sha512-QxhVqhHhN4guA24MlvlsFTb96pE/X8gBd5RBfp4j1kn5tAu1nJ7A9itKcRAuEw0WYJHcsWtI+WUnd7JPioHtlw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + '@rspress/plugin-llms@2.0.21': + resolution: {integrity: sha512-Uc+3TLqgJU43X3ryC6Aw+rzvGe2O3feYJ9eyoDYFKbcIRKezcq1nW1axiaHNXDbr3g584myslDF+gYjg7/9RHw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rspress/core': ^2.0.10 + + '@rspress/plugin-sitemap@2.0.21': + resolution: {integrity: sha512-PldjjLDFqtQZ3yPHHsWxmxGNlmoNH7nSx76ww7QkPzkO9rPrlr71sBLZR3MMc1s6cd5Csyd+gHjfIJiP7o77XQ==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rspress/core': ^2.0.10 + + '@rspress/plugin-twoslash@2.0.21': + resolution: {integrity: sha512-CiWy+HdNnhz4unMJPveGOGEzYEsdZJJ6LVAWpTsM+zh8sIgOdm045BVmBJbWvCnuHVfZIIFLBNC8ZI6323OoOQ==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rspress/core': ^2.0.10 + react: '>=18.0.0' + typescript: ^6.0.3 + + '@rspress/plugin-typedoc@2.0.21': + resolution: {integrity: sha512-xM53g9wI9vl1mDirYn1wfTnh0Q+Fb+zkp0VxHYVQV8lJf/sHvxerf5CHlVSznBdd4Q1BMC5g/kIqdWGs7mqNYQ==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rspress/core': ^2.0.10 + + '@rspress/shared@2.0.21': + resolution: {integrity: sha512-siCWkv1a4n6y7JdVRII+fwCRZGZtG5KGFfKi2w/Tt33UnIgh4XGqozZ5F/K0iZ/QUwg0wi93SoVdOQm2Q+wf8w==} + '@rstackjs/load-config@0.1.2': resolution: {integrity: sha512-6hChPVosmh2rzEt1M1CvGpsaE/+gGlt51ci5pbyd4Bd1FXyH+Owlg99ECvdcWtD7zdDwDM3jGkQL05xn9oWSIA==} peerDependencies: @@ -1195,10 +1298,16 @@ packages: resolution: {integrity: sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ==} engines: {node: '>=20'} + '@shikijs/engine-oniguruma@3.23.0': + resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} + '@shikijs/engine-oniguruma@4.4.3': resolution: {integrity: sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w==} engines: {node: '>=20'} + '@shikijs/langs@3.23.0': + resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} + '@shikijs/langs@4.4.3': resolution: {integrity: sha512-ePic0yfAJGOF83D5wBHK/00EjK65oahBYxFk5epgq33WRv7X9UuxLEV8PtR0szC0z8dl7INIpIodB99JRFlR+A==} engines: {node: '>=20'} @@ -1207,10 +1316,30 @@ packages: resolution: {integrity: sha512-m0wBeLDQDeIxRdUmrCPdQqfuUamDwRL5isCfYbguKD6NiaKpVbsv+3J81DyIKgNW5h4WAIIr8T4EkgQrBBxvaQ==} engines: {node: '>=20'} + '@shikijs/rehype@4.4.3': + resolution: {integrity: sha512-vkG9jG1aRnrx05R31uAOKQHE8qpY7r1cBXE2sAZgFK2IaPnQHwaP4L1C6amQixmZ8thBsKwfbSsYxMDoo46UWQ==} + engines: {node: '>=20'} + + '@shikijs/themes@3.23.0': + resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} + '@shikijs/themes@4.4.3': resolution: {integrity: sha512-w8UHjeUnIR965KMWJHUPXOc2mNJUnK3vpVLYLvw5IYU2mnTTJ89E24OrJDBNiJDQ0qzb0tc4l7mrIXx5cFeIyw==} engines: {node: '>=20'} + '@shikijs/transformers@4.4.3': + resolution: {integrity: sha512-oJSARV6NaWd+rnNJbtnpAdj3Zg0ZVyzsnMgb3vi3HA+35y8lBWUCpOnWsmyiXZIikY+x1BDqrQUgmxfzWh7Jvw==} + engines: {node: '>=20'} + + '@shikijs/twoslash@4.4.3': + resolution: {integrity: sha512-m7HNzunEIHRk1jCya3ngGsO3+8pYxrPIIxtdJewg/W8ceW/+m/mSsm4jM3L9DvYYNa8Rvbu7Dabt3BOpCclz8Q==} + engines: {node: '>=20'} + peerDependencies: + typescript: '>=5.5.0' + + '@shikijs/types@3.23.0': + resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} + '@shikijs/types@4.4.3': resolution: {integrity: sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g==} engines: {node: '>=20'} @@ -1271,6 +1400,9 @@ packages: '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdx@2.0.14': + resolution: {integrity: sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==} + '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} @@ -1426,13 +1558,28 @@ packages: cpu: [x64] os: [win32] + '@typescript/vfs@1.6.4': + resolution: {integrity: sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ==} + peerDependencies: + typescript: '*' + '@ungap/structured-clone@1.3.3': resolution: {integrity: sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==} + '@unhead/react@2.1.17': + resolution: {integrity: sha512-KmcYksDjlLozL0fxUjIDwH/0k6+Lg2HWHLUPlkRW8Gl08hdB1AbLB5iU9QxFvkaT8p0Q4dFEqZWnz3g6RMeiRA==} + peerDependencies: + react: '>=18.3.1' + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn@8.18.0: resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} engines: {node: '>=0.4.0'} @@ -1468,13 +1615,24 @@ packages: any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + before-after-hook@4.0.0: resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} @@ -1482,6 +1640,13 @@ packages: resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} engines: {node: '>=18'} + body-scroll-lock@4.0.0-beta.0: + resolution: {integrity: sha512-a7tP5+0Mw3YlUJcGAKUqIBkYYGlYxk2fnCasq/FUph1hadxlTRjF+gAcZksxANnaMnALjxEddmSi/H3OR8ugcQ==} + + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + engines: {node: 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -1552,6 +1717,13 @@ packages: cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1574,6 +1746,9 @@ packages: resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} engines: {node: '>=22.12.0'} + compute-scroll-into-view@3.1.1: + resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} + content-disposition@1.1.0: resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} engines: {node: '>=18'} @@ -1601,6 +1776,13 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + copy-to-clipboard@3.3.3: + resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} + cors@2.8.6: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} @@ -1678,6 +1860,14 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + env-editor@1.3.0: resolution: {integrity: sha512-EqiD/j01PooUbeWk+etUo2TWoocjoxMfGNYpS9e47glIJ5r8WepycIki+LCbonFbPdwlqY5ETeSTAJVMih4z4w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1701,6 +1891,12 @@ packages: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1712,9 +1908,27 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} + estree-util-attach-comments@3.0.0: + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + estree-util-is-identifier-name@3.0.0: resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + estree-util-scope@1.0.1: + resolution: {integrity: sha512-B0np3dcdxqILX5e9nEi5/Fr4K7gL4oYFVPV1zRa2e9wRCbQoZZNWOZFYyoInvXUPJXBXjss+QXlWLJChDEHDkA==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} @@ -1794,6 +2008,9 @@ packages: resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} engines: {node: '>= 18.0.0'} + flexsearch@0.8.212: + resolution: {integrity: sha512-wSyJr1GUWoOOIISRu+X2IXiOcVfg9qqBRyCPRUdLMIGJqPzMo+jMRlvE83t14v1j0dRMEaBbER/adQjp6Du2pw==} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -1814,6 +2031,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1846,15 +2067,42 @@ packages: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-heading-rank@3.0.0: + resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + + hast-util-to-estree@3.1.3: + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + hast-util-to-html@9.0.5: resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} hast-util-to-jsx-runtime@2.3.6: resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + hast-util-to-parse5@8.0.1: + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} + + hast-util-to-string@3.0.1: + resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} + hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} @@ -1862,6 +2110,9 @@ packages: resolution: {integrity: sha512-r8AO2mYHoLxSHkgafNeC/BXyb2vWRxD3jem4Ts+ptav8oTG5FIRifAjuJEmZI4bSvvc2ns0GxmIYiZnHqN3mMw==} engines: {node: '>=16.9.0'} + hookable@6.1.1: + resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} + html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} @@ -1905,6 +2156,10 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-absolute-url@4.0.1: + resolution: {integrity: sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -1998,6 +2253,9 @@ packages: resolution: {integrity: sha512-Zvpvd56i9FRV5kaJFiiY1t+FNMEH+dGEaLyQprqKlGHBAxJXmdSk+8tVsh6b9YlxbfyyuLrhJCkzwB+AmOBZ0g==} engines: {node: '>=20'} + linkify-it@5.0.2: + resolution: {integrity: sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -2005,6 +2263,17 @@ packages: resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} + lunr@2.3.9: + resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + + markdown-it@14.3.1: + resolution: {integrity: sha512-4Ej49aYTDFIQ+uBkfX8GBvJGccoARxxPep+7aWTs55ozbjQJpW9M26Fe53vnGgvLeVzva/amzjQQaQu9w0vMhA==} + hasBin: true + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -2053,6 +2322,9 @@ packages: mdast-util-mdx-jsx@3.2.0: resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + mdast-util-mdxjs-esm@2.0.1: resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} @@ -2062,16 +2334,40 @@ packages: mdast-util-to-hast@13.2.1: resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + mdast-util-to-markdown-cjk-friendly-gfm-strikethrough@1.0.0: + resolution: {integrity: sha512-1ePVfB4P/vz3xSsm6H3D32r6VYGErxclnuLLFK02/2ReF+UdEKm7caulK6Vm0LBIp5gPRtB2Z1OYDznCkX3k2w==} + engines: {node: '>=18'} + peerDependencies: + '@types/mdast': '*' + peerDependenciesMeta: + '@types/mdast': + optional: true + + mdast-util-to-markdown-cjk-friendly@1.0.0: + resolution: {integrity: sha512-BoaAm8mlJ+LAYz0Qs532Y3ciTuQYgBUPZcSFbvC/ZKmEMAKgulw84YvQK1gI34t/vL2euSfuaWlqczkTBgamkw==} + engines: {node: '>=18'} + peerDependencies: + '@types/mdast': '*' + peerDependenciesMeta: + '@types/mdast': + optional: true + mdast-util-to-markdown@2.1.2: resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdurl@2.1.0: + resolution: {integrity: sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==} + media-typer@1.1.1: resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} engines: {node: '>= 0.8'} + medium-zoom@1.1.0: + resolution: {integrity: sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ==} + merge-descriptors@2.0.0: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} @@ -2083,6 +2379,35 @@ packages: micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + micromark-extension-cjk-friendly-gfm-strikethrough@2.0.1: + resolution: {integrity: sha512-wVC0zwjJNqQeX+bb07YTPu/CvSAyCTafyYb7sMhX1r62/Lw5M/df3JyYaANyp8g15c1ypJRFSsookTqA1IDsUg==} + engines: {node: '>=18'} + peerDependencies: + micromark: ^4.0.0 + micromark-util-types: ^2.0.0 + peerDependenciesMeta: + micromark-util-types: + optional: true + + micromark-extension-cjk-friendly-util@3.0.1: + resolution: {integrity: sha512-GcbXqTTHOsiZHyF753oIddP/J2eH8j9zpyQPhkof6B2JNxfEJabnQqxbCgzJNuNes0Y2jTNJ3LiYPSXr6eJA8w==} + engines: {node: '>=18'} + peerDependencies: + micromark-util-types: '*' + peerDependenciesMeta: + micromark-util-types: + optional: true + + micromark-extension-cjk-friendly@2.0.1: + resolution: {integrity: sha512-OkzoYVTL1ChbvQ8Cc1ayTIz7paFQz8iS9oIYmewncweUSwmWR+hkJF9spJ1lxB90XldJl26A1F4IkPOKS3bDXw==} + engines: {node: '>=18'} + peerDependencies: + micromark: ^4.0.0 + micromark-util-types: ^2.0.0 + peerDependenciesMeta: + micromark-util-types: + optional: true + micromark-extension-gfm-autolink-literal@2.1.0: resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} @@ -2104,12 +2429,30 @@ packages: micromark-extension-gfm@3.0.0: resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + micromark-extension-mdx-expression@3.0.1: + resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} + + micromark-extension-mdx-jsx@3.0.2: + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + micromark-factory-destination@2.0.1: resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} micromark-factory-label@2.0.1: resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + micromark-factory-mdx-expression@2.0.3: + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} + micromark-factory-space@2.0.1: resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} @@ -2140,6 +2483,9 @@ packages: micromark-util-encode@2.0.1: resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + micromark-util-events-to-acorn@2.0.3: + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} + micromark-util-html-tag-name@2.0.1: resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} @@ -2176,6 +2522,10 @@ packages: resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} engines: {node: '>=18'} + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -2213,6 +2563,9 @@ packages: resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} engines: {node: '>=18'} + nprogress@0.2.0: + resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2261,6 +2614,9 @@ packages: parse5@6.0.1: resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -2332,6 +2688,10 @@ packages: engines: {node: '>=18'} hasBin: true + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + pure-rand@8.4.2: resolution: {integrity: sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==} @@ -2355,16 +2715,47 @@ packages: peerDependencies: react: ^19.2.8 + react-lazy-with-preload@2.2.1: + resolution: {integrity: sha512-ONSb8gizLE5jFpdHAclZ6EAAKuFX2JydnFXPPPjoUImZlLjGtKzyBS8SJgJq7CpLgsGKh9QCZdugJyEEOVC16Q==} + react-markdown@10.1.0: resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} peerDependencies: '@types/react': '>=18' react: '>=18' + react-reconciler@0.33.0: + resolution: {integrity: sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==} + engines: {node: '>=0.10.0'} + peerDependencies: + react: ^19.2.0 + react-refresh@0.18.0: resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} engines: {node: '>=0.10.0'} + react-render-to-markdown@19.1.0: + resolution: {integrity: sha512-dF9b3tO41ezqdmHP8X92kbHbMexJ6iC7iHw4ykC8fwiO7DgpFc9PhMoKlI+BcPzRxGcWgQSdrixVB9RykhjJpQ==} + peerDependencies: + react: '>=19' + + react-router-dom@7.18.3: + resolution: {integrity: sha512-ytVbyBBM7vMfRCam25r0WMhSVSom909A8p+8m0/f1w853dz/xfFu6etAT2SEbVoSnI+ZoPRDqIsQXVT89gp7kg==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.18.3: + resolution: {integrity: sha512-gyXgtdr5uACJ5b1Q4udzjVV+tb/rlHIMJKuJ0e89R4Kzgz47z/rgP0dIKxktqIEUhDHluGTPJJH/wRha7CyqsA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + react-server-dom-rspack@0.1.0: resolution: {integrity: sha512-KqDzmxBUZEcAphwg/PnEHOBkqTJjesTmLoBygLHie1gkiLINiZuVKPRccS6qDzfdj9ccYCaJ743IDYVh0wPf/w==} engines: {node: '>=0.10.0'} @@ -2381,6 +2772,20 @@ packages: resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} engines: {node: '>= 20.19.0'} + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.1: + resolution: {integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + regex-recursion@6.0.2: resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} @@ -2390,9 +2795,41 @@ packages: regex@6.1.0: resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + rehype-external-links@3.0.0: + resolution: {integrity: sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==} + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + + remark-cjk-friendly-gfm-strikethrough@2.3.1: + resolution: {integrity: sha512-JE3TGgouk/sy92SemNMEUhO5mNP4on04cmzOV3s3R5Dbk160ewmpM4tgPiinKKvoJ5UW2fTu7FOYsjVbusSA9w==} + engines: {node: '>=18'} + peerDependencies: + '@types/mdast': ^4.0.0 + unified: ^11.0.0 + peerDependenciesMeta: + '@types/mdast': + optional: true + + remark-cjk-friendly@2.3.1: + resolution: {integrity: sha512-f+pKZRxCRwNEGFBKNRAZAqU91GIK1SAo3ZyFHWRUgC9zcxRR0BXKd6YwqgSsxtW0rNpUDtONj7H5nje2WL3fcA==} + engines: {node: '>=18'} + peerDependencies: + '@types/mdast': ^4.0.0 + unified: ^11.0.0 + peerDependenciesMeta: + '@types/mdast': + optional: true + remark-gfm@4.0.1: resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + remark-mdx@3.1.1: + resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} + remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} @@ -2466,6 +2903,9 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + scroll-into-view-if-needed@3.1.0: + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + semver@7.8.5: resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} @@ -2479,6 +2919,9 @@ packages: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -2529,6 +2972,10 @@ packages: resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} engines: {node: '>=8'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} @@ -2584,6 +3031,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toggle-selection@1.0.6: + resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -2601,10 +3051,31 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + twoslash-protocol@0.3.9: + resolution: {integrity: sha512-9/iwp+CXOnjFMPQuPL5PkuRbZnDoNpBvtJCLs9t8kDYkL3YHujbvnHfZA1i5fApDftVEdBw+T/4F+dH5kIzpYQ==} + + twoslash@0.3.9: + resolution: {integrity: sha512-rDclk+OtzuTX+tnea7DYLCkqGQ3eP0IyfD+kzUJ7t46X/NzlaxwrhecmEBNuSCuEn3V+n1PhcjUUQQ7gUJzX5Q==} + peerDependencies: + typescript: ^5.5.0 || ^6.0.0 + type-is@2.1.0: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} + typedoc-plugin-markdown@4.12.0: + resolution: {integrity: sha512-eJDEMAfxCmede22c/Jw7d0FA13ggAQv+KkwQYKYCdqI02cin6Rc9QRwbG/7XvvHWinuFejySnZVUWDtvGk3Vbg==} + engines: {node: '>= 18'} + peerDependencies: + typedoc: 0.28.x + + typedoc@0.28.20: + resolution: {integrity: sha512-uSKqkh8Cr48vllnEy+jdaAgOeR6Y+QCBW7usgUsKj7gJEfR7stw9U/fE49LBnj2tPRKPY0c0EBJSWe9Appmplg==} + engines: {node: '>= 18', pnpm: '>= 10'} + hasBin: true + peerDependencies: + typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x || 6.0.x + typescript@5.6.1-rc: resolution: {integrity: sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==} engines: {node: '>=14.17'} @@ -2615,11 +3086,19 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + typescript@7.0.2: resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} engines: {node: '>=16.20.0'} hasBin: true + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} @@ -2627,6 +3106,9 @@ packages: resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} engines: {node: '>=20.18.1'} + unhead@2.1.17: + resolution: {integrity: sha512-HLMKXOszRhAPBrr6VlqCeVeJq2kbC4kXwzGLEZvvojPLWNYTJw22xG7Bfwhsvs31+IBet3Wl8ADg9dwYdyphfQ==} + unicode-emoji-modifier-base@1.0.0: resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} engines: {node: '>=4'} @@ -2645,12 +3127,18 @@ packages: unist-util-is@6.0.1: resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + unist-util-position@5.0.0: resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} unist-util-stringify-position@4.0.0: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + unist-util-visit-children@3.0.0: + resolution: {integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==} + unist-util-visit-parents@6.0.2: resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} @@ -2672,12 +3160,18 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2978,6 +3472,25 @@ snapshots: tslib: 2.8.1 optional: true + '@floating-ui/core@1.8.0': + dependencies: + '@floating-ui/utils': 0.2.12 + + '@floating-ui/dom@1.8.0': + dependencies: + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 + + '@floating-ui/utils@0.2.12': {} + + '@gerrit0/mini-shiki@3.23.0': + dependencies: + '@shikijs/engine-oniguruma': 3.23.0 + '@shikijs/langs': 3.23.0 + '@shikijs/themes': 3.23.0 + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@hono/node-server@1.19.17(hono@4.13.3)': dependencies: hono: 4.13.3 @@ -3014,6 +3527,42 @@ snapshots: tinyglobby: 0.2.17 yaml: 2.9.0 + '@mdx-js/mdx@3.1.1(supports-color@7.2.0)': + dependencies: + '@types/estree': 1.0.9 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdx': 2.0.14 + acorn: 8.18.0 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.1 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6(supports-color@7.2.0) + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.1(acorn@8.18.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0(supports-color@7.2.0) + remark-mdx: 3.1.1(supports-color@7.2.0) + remark-parse: 11.0.0(supports-color@7.2.0) + remark-rehype: 11.1.2 + source-map: 0.7.6 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@mdx-js/react@3.1.1(@types/react@19.2.18)(react@19.2.8)': + dependencies: + '@types/mdx': 2.0.14 + '@types/react': 19.2.18 + react: 19.2.8 + '@modelcontextprotocol/client@2.0.0': dependencies: '@modelcontextprotocol/core': 2.0.0 @@ -3416,20 +3965,118 @@ snapshots: optionalDependencies: '@rspack/core': 2.2.1(@swc/helpers@0.5.23) - '@rstackjs/load-config@0.1.2(jiti@2.7.0)': - optionalDependencies: - jiti: 2.7.0 - - '@rstest/adapter-rslib@0.11.10(@rslib/core@0.23.2(typescript@7.0.2))(@rstest/core@0.11.10)(typescript@7.0.2)': - dependencies: - '@rslib/core': 0.23.2(typescript@7.0.2) - '@rstest/core': 0.11.10 - optionalDependencies: - typescript: 7.0.2 - - '@rstest/browser-react@0.11.10(@rstest/core@0.11.10)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@rspress/core@2.0.21(@rspack/core@2.2.1(@swc/helpers@0.5.23))(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@rstest/core': 0.11.10 + '@mdx-js/mdx': 3.1.1(supports-color@7.2.0) + '@mdx-js/react': 3.1.1(@types/react@19.2.18)(react@19.2.8) + '@rsbuild/core': 2.2.1 + '@rsbuild/plugin-react': 2.1.0(@rsbuild/core@2.2.1)(@rspack/core@2.2.1(@swc/helpers@0.5.23)) + '@rspress/shared': 2.0.21(supports-color@7.2.0) + '@shikijs/rehype': 4.4.3 + '@types/mdast': 4.0.4 + '@types/react': 19.2.18 + '@unhead/react': 2.1.17(react@19.2.8) + body-scroll-lock: 4.0.0-beta.0 + clsx: 2.1.1 + copy-to-clipboard: 3.3.3 + flexsearch: 0.8.212 + hast-util-heading-rank: 3.0.0 + hast-util-to-jsx-runtime: 2.3.6(supports-color@7.2.0) + mdast-util-mdx: 3.0.0(supports-color@7.2.0) + mdast-util-mdxjs-esm: 2.0.1(supports-color@7.2.0) + medium-zoom: 1.1.0 + nprogress: 0.2.0 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + react-lazy-with-preload: 2.2.1 + react-render-to-markdown: 19.1.0(react@19.2.8) + react-router-dom: 7.18.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + rehype-external-links: 3.0.0 + rehype-raw: 7.0.0 + remark-cjk-friendly: 2.3.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(unified@11.0.5) + remark-cjk-friendly-gfm-strikethrough: 2.3.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(supports-color@7.2.0)(unified@11.0.5) + remark-gfm: 4.0.1(supports-color@7.2.0) + remark-mdx: 3.1.1(supports-color@7.2.0) + remark-parse: 11.0.0(supports-color@7.2.0) + remark-stringify: 11.0.0 + scroll-into-view-if-needed: 3.1.0 + shiki: 4.4.3 + unified: 11.0.5 + unist-util-visit: 5.1.0 + unist-util-visit-children: 3.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - '@module-federation/runtime-tools' + - '@rspack/core' + - core-js + - micromark + - micromark-util-types + - supports-color + + '@rspress/plugin-llms@2.0.21(@rspress/core@2.0.21(@rspack/core@2.2.1(@swc/helpers@0.5.23))(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(supports-color@7.2.0))(supports-color@7.2.0)': + dependencies: + '@rspress/core': 2.0.21(@rspack/core@2.2.1(@swc/helpers@0.5.23))(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(supports-color@7.2.0) + remark-mdx: 3.1.1(supports-color@7.2.0) + remark-parse: 11.0.0(supports-color@7.2.0) + remark-stringify: 11.0.0 + unified: 11.0.5 + unist-util-visit: 5.1.0 + transitivePeerDependencies: + - supports-color + + '@rspress/plugin-sitemap@2.0.21(@rspress/core@2.0.21(@rspack/core@2.2.1(@swc/helpers@0.5.23))(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(supports-color@7.2.0))': + dependencies: + '@rspress/core': 2.0.21(@rspack/core@2.2.1(@swc/helpers@0.5.23))(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(supports-color@7.2.0) + + '@rspress/plugin-twoslash@2.0.21(@rspress/core@2.0.21(@rspack/core@2.2.1(@swc/helpers@0.5.23))(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(supports-color@7.2.0))(react@19.2.8)(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@floating-ui/dom': 1.8.0 + '@rspress/core': 2.0.21(@rspack/core@2.2.1(@swc/helpers@0.5.23))(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(supports-color@7.2.0) + '@shikijs/twoslash': 4.4.3(supports-color@7.2.0)(typescript@6.0.3) + mdast-util-from-markdown: 2.0.3(supports-color@7.2.0) + mdast-util-gfm: 3.1.0(supports-color@7.2.0) + mdast-util-to-hast: 13.2.1 + picocolors: 1.1.1 + react: 19.2.8 + twoslash: 0.3.9(supports-color@7.2.0)(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@rspress/plugin-typedoc@2.0.21(@rspress/core@2.0.21(@rspack/core@2.2.1(@swc/helpers@0.5.23))(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(supports-color@7.2.0))(typescript@6.0.3)': + dependencies: + '@rspress/core': 2.0.21(@rspack/core@2.2.1(@swc/helpers@0.5.23))(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(supports-color@7.2.0) + typedoc: 0.28.20(typescript@6.0.3) + typedoc-plugin-markdown: 4.12.0(typedoc@0.28.20(typescript@6.0.3)) + transitivePeerDependencies: + - typescript + + '@rspress/shared@2.0.21(supports-color@7.2.0)': + dependencies: + '@rsbuild/core': 2.2.1 + '@shikijs/rehype': 4.4.3 + '@types/react': 19.2.18 + mdast-util-mdx-jsx: 3.2.0(supports-color@7.2.0) + unified: 11.0.5 + transitivePeerDependencies: + - '@module-federation/runtime-tools' + - core-js + - supports-color + + '@rstackjs/load-config@0.1.2(jiti@2.7.0)': + optionalDependencies: + jiti: 2.7.0 + + '@rstest/adapter-rslib@0.11.10(@rslib/core@0.23.2(typescript@7.0.2))(@rstest/core@0.11.10)(typescript@7.0.2)': + dependencies: + '@rslib/core': 0.23.2(typescript@7.0.2) + '@rstest/core': 0.11.10 + optionalDependencies: + typescript: 7.0.2 + + '@rstest/browser-react@0.11.10(@rstest/core@0.11.10)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rstest/core': 0.11.10 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) @@ -3477,11 +4124,20 @@ snapshots: '@shikijs/vscode-textmate': 10.0.2 oniguruma-to-es: 4.3.6 + '@shikijs/engine-oniguruma@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@shikijs/engine-oniguruma@4.4.3': dependencies: '@shikijs/types': 4.4.3 '@shikijs/vscode-textmate': 10.0.2 + '@shikijs/langs@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/langs@4.4.3': dependencies: '@shikijs/types': 4.4.3 @@ -3492,10 +4148,42 @@ snapshots: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.5 + '@shikijs/rehype@4.4.3': + dependencies: + '@shikijs/types': 4.4.3 + '@types/hast': 3.0.5 + hast-util-to-string: 3.0.1 + shiki: 4.4.3 + unified: 11.0.5 + unist-util-visit: 5.1.0 + + '@shikijs/themes@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/themes@4.4.3': dependencies: '@shikijs/types': 4.4.3 + '@shikijs/transformers@4.4.3': + dependencies: + '@shikijs/core': 4.4.3 + '@shikijs/types': 4.4.3 + + '@shikijs/twoslash@4.4.3(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@shikijs/core': 4.4.3 + '@shikijs/types': 4.4.3 + twoslash: 0.3.9(supports-color@7.2.0)(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@shikijs/types@3.23.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + '@shikijs/types@4.4.3': dependencies: '@shikijs/vscode-textmate': 10.0.2 @@ -3567,6 +4255,8 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/mdx@2.0.14': {} + '@types/ms@2.1.0': {} '@types/node@26.4.0': @@ -3662,13 +4352,29 @@ snapshots: '@typescript/typescript-win32-x64@7.0.2': optional: true + '@typescript/vfs@1.6.4(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + debug: 4.4.3(supports-color@7.2.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + '@ungap/structured-clone@1.3.3': {} + '@unhead/react@2.1.17(react@19.2.8)': + dependencies: + react: 19.2.8 + unhead: 2.1.17 + accepts@2.0.0: dependencies: mime-types: 3.0.2 negotiator: 1.1.0 + acorn-jsx@5.3.2(acorn@8.18.0): + dependencies: + acorn: 8.18.0 + acorn@8.18.0: {} ajv-formats@3.0.1(ajv@8.20.0): @@ -3696,10 +4402,16 @@ snapshots: any-promise@1.3.0: {} + argparse@2.0.1: {} + assertion-error@2.0.1: {} + astring@1.9.0: {} + bail@2.0.2: {} + balanced-match@4.0.4: {} + before-after-hook@4.0.0: {} body-parser@2.3.0(supports-color@7.2.0): @@ -3716,6 +4428,12 @@ snapshots: transitivePeerDependencies: - supports-color + body-scroll-lock@4.0.0-beta.0: {} + + brace-expansion@5.0.9: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -3784,6 +4502,10 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clsx@2.1.1: {} + + collapse-white-space@2.1.0: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -3798,6 +4520,8 @@ snapshots: commander@15.0.0: {} + compute-scroll-into-view@3.1.1: {} + content-disposition@1.1.0: {} content-type@1.0.5: {} @@ -3812,6 +4536,12 @@ snapshots: cookie@0.7.2: {} + cookie@1.1.1: {} + + copy-to-clipboard@3.3.3: + dependencies: + toggle-selection: 1.0.6 + cors@2.8.6: dependencies: object-assign: 4.1.1 @@ -3879,6 +4609,10 @@ snapshots: encodeurl@2.0.0: {} + entities@4.5.0: {} + + entities@6.0.1: {} + env-editor@1.3.0: {} environment@1.1.0: {} @@ -3893,14 +4627,59 @@ snapshots: dependencies: es-errors: 1.3.0 + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.18.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.3 + escalade@3.2.0: {} escape-html@1.0.3: {} escape-string-regexp@5.0.0: {} + estree-util-attach-comments@3.0.0: + dependencies: + '@types/estree': 1.0.9 + + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + estree-util-is-identifier-name@3.0.0: {} + estree-util-scope@1.0.1: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + etag@1.8.1: {} eventsource-parser@3.1.1: {} @@ -4022,6 +4801,8 @@ snapshots: transitivePeerDependencies: - supports-color + flexsearch@0.8.212: {} + forwarded@0.2.0: {} fresh@2.0.0: {} @@ -4033,6 +4814,8 @@ snapshots: get-caller-file@2.0.5: {} + get-east-asian-width@1.6.0: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4070,6 +4853,66 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.2.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-heading-rank@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.5 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.3 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.1 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-estree@3.1.3(supports-color@7.2.0): + dependencies: + '@types/estree': 1.0.9 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1(supports-color@7.2.0) + mdast-util-mdx-jsx: 3.2.0(supports-color@7.2.0) + mdast-util-mdxjs-esm: 2.0.1(supports-color@7.2.0) + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + hast-util-to-html@9.0.5: dependencies: '@types/hast': 3.0.5 @@ -4104,14 +4947,38 @@ snapshots: transitivePeerDependencies: - supports-color + hast-util-to-parse5@8.0.1: + dependencies: + '@types/hast': 3.0.5 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-string@3.0.1: + dependencies: + '@types/hast': 3.0.5 + hast-util-whitespace@3.0.0: dependencies: '@types/hast': 3.0.5 + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.5 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + highlight.js@10.7.3: {} hono@4.13.3: {} + hookable@6.1.1: {} + html-url-attributes@3.0.1: {} html-void-elements@3.0.0: {} @@ -4144,6 +5011,8 @@ snapshots: ipaddr.js@1.9.1: {} + is-absolute-url@4.0.1: {} + is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -4210,10 +5079,27 @@ snapshots: dependencies: unicorn-magic: 0.4.0 + linkify-it@5.0.2: + dependencies: + uc.micro: 2.1.0 + longest-streak@3.1.0: {} lru-cache@11.5.2: {} + lunr@2.3.9: {} + + markdown-extensions@2.0.0: {} + + markdown-it@14.3.1: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.2 + mdurl: 2.1.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + markdown-table@3.0.4: {} marked-terminal@7.3.0(marked@9.1.6): @@ -4340,6 +5226,16 @@ snapshots: transitivePeerDependencies: - supports-color + mdast-util-mdx@3.0.0(supports-color@7.2.0): + dependencies: + mdast-util-from-markdown: 2.0.3(supports-color@7.2.0) + mdast-util-mdx-expression: 2.0.1(supports-color@7.2.0) + mdast-util-mdx-jsx: 3.2.0(supports-color@7.2.0) + mdast-util-mdxjs-esm: 2.0.1(supports-color@7.2.0) + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + mdast-util-mdxjs-esm@2.0.1(supports-color@7.2.0): dependencies: '@types/estree-jsx': 1.0.5 @@ -4368,6 +5264,28 @@ snapshots: unist-util-visit: 5.1.0 vfile: 6.0.3 + mdast-util-to-markdown-cjk-friendly-gfm-strikethrough@1.0.0(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(supports-color@7.2.0): + dependencies: + mdast-util-gfm-strikethrough: 2.0.0(supports-color@7.2.0) + mdast-util-to-markdown: 2.1.2 + micromark-extension-cjk-friendly-util: 3.0.1(micromark-util-types@2.0.2) + micromark-util-symbol: 2.0.1 + optionalDependencies: + '@types/mdast': 4.0.4 + transitivePeerDependencies: + - micromark-util-types + - supports-color + + mdast-util-to-markdown-cjk-friendly@1.0.0(@types/mdast@4.0.4)(micromark-util-types@2.0.2): + dependencies: + mdast-util-to-markdown: 2.1.2 + micromark-extension-cjk-friendly-util: 3.0.1(micromark-util-types@2.0.2) + micromark-util-symbol: 2.0.1 + optionalDependencies: + '@types/mdast': 4.0.4 + transitivePeerDependencies: + - micromark-util-types + mdast-util-to-markdown@2.1.2: dependencies: '@types/mdast': 4.0.4 @@ -4384,8 +5302,12 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + mdurl@2.1.0: {} + media-typer@1.1.1: {} + medium-zoom@1.1.0: {} + merge-descriptors@2.0.0: {} merge2@1.4.1: {} @@ -4409,6 +5331,38 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + micromark-extension-cjk-friendly-gfm-strikethrough@2.0.1(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0)): + dependencies: + devlop: 1.1.0 + get-east-asian-width: 1.6.0 + micromark: 4.0.2(supports-color@7.2.0) + micromark-extension-cjk-friendly-util: 3.0.1(micromark-util-types@2.0.2) + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + optionalDependencies: + micromark-util-types: 2.0.2 + + micromark-extension-cjk-friendly-util@3.0.1(micromark-util-types@2.0.2): + dependencies: + get-east-asian-width: 1.6.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + optionalDependencies: + micromark-util-types: 2.0.2 + + micromark-extension-cjk-friendly@2.0.1(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0)): + dependencies: + devlop: 1.1.0 + micromark: 4.0.2(supports-color@7.2.0) + micromark-extension-cjk-friendly-util: 3.0.1(micromark-util-types@2.0.2) + micromark-util-chunked: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + optionalDependencies: + micromark-util-types: 2.0.2 + micromark-extension-gfm-autolink-literal@2.1.0: dependencies: micromark-util-character: 2.1.1 @@ -4467,6 +5421,57 @@ snapshots: micromark-util-combine-extensions: 2.0.1 micromark-util-types: 2.0.2 + micromark-extension-mdx-expression@3.0.1: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + micromark-factory-destination@2.0.1: dependencies: micromark-util-character: 2.1.1 @@ -4480,6 +5485,18 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + micromark-factory-space@2.0.1: dependencies: micromark-util-character: 2.1.1 @@ -4532,6 +5549,16 @@ snapshots: micromark-util-encode@2.0.1: {} + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.9 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + micromark-util-html-tag-name@2.0.1: {} micromark-util-normalize-identifier@2.0.1: @@ -4592,6 +5619,10 @@ snapshots: dependencies: mime-db: 1.54.0 + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.9 + mri@1.2.0: {} mrmime@2.0.1: {} @@ -4641,6 +5672,8 @@ snapshots: path-key: 4.0.0 unicorn-magic: 0.3.0 + nprogress@0.2.0: {} + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -4699,6 +5732,10 @@ snapshots: parse5@6.0.1: {} + parse5@7.3.0: + dependencies: + entities: 6.0.1 + parseurl@1.3.3: {} path-key@3.1.1: {} @@ -4749,6 +5786,8 @@ snapshots: picocolors: 1.1.1 sade: 1.8.1 + punycode.js@2.3.1: {} + pure-rand@8.4.2: {} qs@6.15.3: @@ -4772,6 +5811,8 @@ snapshots: react: 19.2.8 scheduler: 0.27.0 + react-lazy-with-preload@2.2.1: {} + react-markdown@10.1.0(@types/react@19.2.18)(react@19.2.8)(supports-color@7.2.0): dependencies: '@types/hast': 3.0.5 @@ -4790,8 +5831,32 @@ snapshots: transitivePeerDependencies: - supports-color + react-reconciler@0.33.0(react@19.2.8): + dependencies: + react: 19.2.8 + scheduler: 0.27.0 + react-refresh@0.18.0: {} + react-render-to-markdown@19.1.0(react@19.2.8): + dependencies: + react: 19.2.8 + react-reconciler: 0.33.0(react@19.2.8) + + react-router-dom@7.18.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + dependencies: + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + react-router: 7.18.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + + react-router@7.18.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + dependencies: + cookie: 1.1.1 + react: 19.2.8 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 19.2.8(react@19.2.8) + react-server-dom-rspack@0.1.0(@rspack/core@2.2.1(@swc/helpers@0.5.23))(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: '@rspack/core': 2.2.1(@swc/helpers@0.5.23) @@ -4802,6 +5867,35 @@ snapshots: readdirp@5.1.1: {} + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.9 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.1(acorn@8.18.0): + dependencies: + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.9 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.9 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + regex-recursion@6.0.2: dependencies: regex-utilities: 2.3.0 @@ -4812,6 +5906,52 @@ snapshots: dependencies: regex-utilities: 2.3.0 + rehype-external-links@3.0.0: + dependencies: + '@types/hast': 3.0.5 + '@ungap/structured-clone': 1.3.3 + hast-util-is-element: 3.0.0 + is-absolute-url: 4.0.1 + space-separated-tokens: 2.0.2 + unist-util-visit: 5.1.0 + + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.5 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + rehype-recma@1.0.0(supports-color@7.2.0): + dependencies: + '@types/estree': 1.0.9 + '@types/hast': 3.0.5 + hast-util-to-estree: 3.1.3(supports-color@7.2.0) + transitivePeerDependencies: + - supports-color + + remark-cjk-friendly-gfm-strikethrough@2.3.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(supports-color@7.2.0)(unified@11.0.5): + dependencies: + mdast-util-to-markdown-cjk-friendly-gfm-strikethrough: 1.0.0(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(supports-color@7.2.0) + micromark-extension-cjk-friendly-gfm-strikethrough: 2.0.1(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0)) + unified: 11.0.5 + optionalDependencies: + '@types/mdast': 4.0.4 + transitivePeerDependencies: + - micromark + - micromark-util-types + - supports-color + + remark-cjk-friendly@2.3.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(unified@11.0.5): + dependencies: + mdast-util-to-markdown-cjk-friendly: 1.0.0(@types/mdast@4.0.4)(micromark-util-types@2.0.2) + micromark-extension-cjk-friendly: 2.0.1(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0)) + unified: 11.0.5 + optionalDependencies: + '@types/mdast': 4.0.4 + transitivePeerDependencies: + - micromark + - micromark-util-types + remark-gfm@4.0.1(supports-color@7.2.0): dependencies: '@types/mdast': 4.0.4 @@ -4823,6 +5963,13 @@ snapshots: transitivePeerDependencies: - supports-color + remark-mdx@3.1.1(supports-color@7.2.0): + dependencies: + mdast-util-mdx: 3.0.0(supports-color@7.2.0) + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + remark-parse@11.0.0(supports-color@7.2.0): dependencies: '@types/mdast': 4.0.4 @@ -4894,6 +6041,10 @@ snapshots: scheduler@0.27.0: {} + scroll-into-view-if-needed@3.1.0: + dependencies: + compute-scroll-into-view: 3.1.1 + semver@7.8.5: {} send@1.2.1(supports-color@7.2.0): @@ -4921,6 +6072,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-cookie-parser@2.7.2: {} + setprototypeof@1.2.0: {} shebang-command@2.0.0: @@ -4984,6 +6137,8 @@ snapshots: dependencies: unicode-emoji-modifier-base: 1.0.0 + source-map@0.7.6: {} + space-separated-tokens@2.0.2: {} statuses@2.0.2: {} @@ -5041,6 +6196,8 @@ snapshots: dependencies: is-number: 7.0.0 + toggle-selection@1.0.6: {} + toidentifier@1.0.1: {} totalist@3.0.1: {} @@ -5051,16 +6208,41 @@ snapshots: tslib@2.8.1: {} + twoslash-protocol@0.3.9: {} + + twoslash@0.3.9(supports-color@7.2.0)(typescript@6.0.3): + dependencies: + '@typescript/vfs': 1.6.4(supports-color@7.2.0)(typescript@6.0.3) + twoslash-protocol: 0.3.9 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + type-is@2.1.0: dependencies: content-type: 2.1.0 media-typer: 1.1.1 mime-types: 3.0.2 + typedoc-plugin-markdown@4.12.0(typedoc@0.28.20(typescript@6.0.3)): + dependencies: + typedoc: 0.28.20(typescript@6.0.3) + + typedoc@0.28.20(typescript@6.0.3): + dependencies: + '@gerrit0/mini-shiki': 3.23.0 + lunr: 2.3.9 + markdown-it: 14.3.1 + minimatch: 10.2.6 + typescript: 6.0.3 + yaml: 2.9.0 + typescript@5.6.1-rc: {} typescript@5.9.3: {} + typescript@6.0.3: {} + typescript@7.0.2: optionalDependencies: '@typescript/typescript-aix-ppc64': 7.0.2 @@ -5084,10 +6266,16 @@ snapshots: '@typescript/typescript-win32-arm64': 7.0.2 '@typescript/typescript-win32-x64': 7.0.2 + uc.micro@2.1.0: {} + undici-types@8.3.0: {} undici@7.29.0: {} + unhead@2.1.17: + dependencies: + hookable: 6.1.1 + unicode-emoji-modifier-base@1.0.0: {} unicorn-magic@0.3.0: {} @@ -5108,6 +6296,10 @@ snapshots: dependencies: '@types/unist': 3.0.3 + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-position@5.0.0: dependencies: '@types/unist': 3.0.3 @@ -5116,6 +6308,10 @@ snapshots: dependencies: '@types/unist': 3.0.3 + unist-util-visit-children@3.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit-parents@6.0.2: dependencies: '@types/unist': 3.0.3 @@ -5135,6 +6331,11 @@ snapshots: vary@1.1.2: {} + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 @@ -5145,6 +6346,8 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 + web-namespaces@2.0.1: {} + which@2.0.2: dependencies: isexe: 2.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0ba3a87dd..91f37d737 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,6 +2,7 @@ packages: - packages/* - packages/workbench/src/inspector - examples/* + - website # The published agent-bundle manifest must never carry a `workspace:` range # (npm refuses to install such a tarball, and the release audit forbids it), # so its optional @agent-bundle/runtime peer is satisfied from the workspace diff --git a/scripts/classify-docs-only.mjs b/scripts/classify-docs-only.mjs index 3340ebb12..27d1939f2 100644 --- a/scripts/classify-docs-only.mjs +++ b/scripts/classify-docs-only.mjs @@ -2,15 +2,23 @@ import { appendFile, readFile } from 'node:fs/promises'; import { pathToFileURL } from 'node:url'; /** - * Hosted CI docs-only allowlist. Nested markdown outside docs/ and - * agent-patterns/ is code: examples and packages ship compiled SKILL.md + * Hosted CI docs-only allowlist. Nested markdown outside docs/, website/, + * and agent-patterns/ is code: examples and packages ship compiled SKILL.md * artifacts, and package markdown is part of npm pack audits. * + * website/ is the Rspress documentation site. Its own workflow (docs.yml) + * typechecks and builds it on every PR, so website-only PRs skip the heavy + * package jobs here without losing validation. + * * Globs match the workflow `case` that this script replaced: `*` matches * slashes, so `.changeset/*.md` includes nested changeset markdown. */ export const isDocsOnlyPath = (filePath) => { - if (filePath.startsWith('docs/') || filePath.startsWith('agent-patterns/')) { + if ( + filePath.startsWith('docs/') + || filePath.startsWith('agent-patterns/') + || filePath.startsWith('website/') + ) { return true; } if (filePath.startsWith('.changeset/') && filePath.endsWith('.md')) { diff --git a/website/docs/en/_nav.json b/website/docs/en/_nav.json new file mode 100644 index 000000000..068035516 --- /dev/null +++ b/website/docs/en/_nav.json @@ -0,0 +1,27 @@ +[ + { + "text": "Guide", + "link": "/guide/start/", + "activeMatch": "/guide/" + }, + { + "text": "Reference", + "link": "/reference/", + "activeMatch": "/reference/" + }, + { + "text": "Examples", + "link": "/examples/", + "activeMatch": "/examples/" + }, + { + "text": "Contributing", + "link": "/contributing/", + "activeMatch": "/contributing/" + }, + { + "text": "Type API", + "link": "/api/", + "activeMatch": "/api/" + } +] diff --git a/website/docs/en/api/_meta.json b/website/docs/en/api/_meta.json new file mode 100644 index 000000000..3bb657871 --- /dev/null +++ b/website/docs/en/api/_meta.json @@ -0,0 +1,33 @@ +[ + "index", + { + "type": "dir", + "name": "modules", + "label": "Modules" + }, + { + "type": "dir", + "name": "functions", + "label": "Functions" + }, + { + "type": "dir", + "name": "classes", + "label": "Classes" + }, + { + "type": "dir", + "name": "interfaces", + "label": "Interfaces" + }, + { + "type": "dir", + "name": "types", + "label": "Type aliases" + }, + { + "type": "dir", + "name": "variables", + "label": "Variables" + } +] diff --git a/website/docs/en/contributing/_meta.json b/website/docs/en/contributing/_meta.json new file mode 100644 index 000000000..a7d5eda49 --- /dev/null +++ b/website/docs/en/contributing/_meta.json @@ -0,0 +1 @@ +["index"] diff --git a/website/docs/en/contributing/index.mdx b/website/docs/en/contributing/index.mdx new file mode 100644 index 000000000..4aa07954a --- /dev/null +++ b/website/docs/en/contributing/index.mdx @@ -0,0 +1,136 @@ +--- +description: 'The agent-bundle contributor workflow: the three local gates, the local-merge loop, Changesets, the opt-in native host smokes, and the documentation site.' +--- + +# Contributing + +This page is for people changing **agent-bundle itself**. None of it is a requirement for +authoring a plugin — a plugin author needs `agent-bundle` and Node.js, and nothing on this page. + +The repository is a pnpm workspace. Node.js 22.19 or later is the floor here for the same reason +it is the floor for the framework. + +From a fresh checkout: + +```sh +pnpm install +pnpm build +``` + +`pnpm build` builds the workspace packages the rest of the scripts and every example depend on, +so it comes first after a fresh install. + +## The three gates + +| Command | What it proves | When to run it | +| --- | --- | --- | +| `pnpm check` | The local delivery gate: build, unit, route-unit, projection, and integration tests, lint, typecheck. | While working, and before every push. | +| `pnpm check:release` | Packaging evidence alongside `pnpm check`: `npm pack` dry run, the release audit, and the packed test pools including the scaffolder template matrix. | Before touching packaging, exports, or the published surface. | +| `pnpm check:local-ci` | The full hosted CI gate — the three-Node verify matrix plus the examples, release, and micro-eval jobs — in parallel local worktrees. | As the merge gate. | + +Narrower scripts exist for iteration: `pnpm test:unit`, `pnpm test:route-unit`, +`pnpm test:projection`, `pnpm test:integration`, `pnpm lint`, and `pnpm typecheck`. They are +faster signals, not gates. + +## The local-merge loop + +`pnpm check:local-ci` exists because a hosted verify leg is slow, and because local green is what +merges a branch here. It runs every leg in an isolated git worktree pinned to the branch's HEAD +commit, each with its own `node_modules` and its own temp root: + +1. Run `pnpm check:local-ci` on the branch's HEAD commit — uncommitted changes are **not** + covered, and the runner warns when it finds them. +2. If the gate is green, the branch is mergeable. +3. Hosted CI still runs on the merged commit and stays the asynchronous post-merge safety net. If + it disagrees with the local run, the hosted result wins and the merge gets a follow-up fix. + +For quick iteration, `pnpm check:local-ci --current-node-only` runs one verify-equivalent leg on +the active Node. It skips the Node matrix and the examples, release, and micro-eval gates, so it +is a fast signal rather than a merge gate. `--fresh` recreates the reused leg worktrees when +staleness is suspected. + +The runner resolves each hosted Node line (22.19.x, 24.x, 26.x) from an explicit +`AGENT_BUNDLE_LOCAL_CI_NODE_22` / `_24` / `_26` override first, then `mise`, then `~/.nvm`, +then the current process, and version-checks every binary before use. A missing line fails +with the exact install command rather than silently running the wrong runtime. The leg +worktrees, the per-step logs, and the leg × step × status × duration × test census summary +all live under the gitignored `.worktrees/local-ci/` directory. + +[`docs/local-ci.md`](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/local-ci.md) +is the full contract, including what the local gate deliberately does not cover. + +## Changesets + +Versioning goes through [Changesets](https://github.com/changesets/changesets). Add one in the +same commit as a user-visible change: + +```sh +pnpm changeset +``` + +Pick the affected packages and the bump, and describe the change from the reader's side — a +changeset is release-note copy, not a commit message. `pnpm version-packages` applies pending +changesets, and publishing runs `pnpm check:release` before `changeset publish`. Private +workspace packages, including the examples and this site, are not versioned or tagged. + +Nothing is published to npm yet. The current release channel is the pkg.pr.new preview +tarballs described in [Preview packages](../guide/distribution/preview-packages.mdx). + +## Native host smokes are opt-in + +Some evidence needs a signed-in Claude or Codex CLI on the machine, so it cannot be a gate. Those +smokes are opt-in locally and are deliberately skipped in CI; the hosted workflow that runs them +is `workflow_dispatch`-only and names the host it exercises. + +```sh +pnpm test:packed:native:claude +pnpm test:packed:native:codex +pnpm test:host-install:session:claude +``` + +Everything else in the test matrix is credential-free. `host-install` keeps an unconditional +deterministic adapter-simulator lane, available host binaries additionally prove their public +install paths, and Cursor records its unavailable non-interactive host-session surface explicitly +rather than reporting a pass it did not earn. That boundary is the whole point of the proof +levels in [Testing](../guide/development/testing.mdx): a level is never reported as a receipt for +a stronger one. + +Dependency review, package previews, and the release publish workflow are hosted-only for +structural reasons — the first reads GitHub's advisory database against the pull-request diff, +and the other two are publish-side effects rather than checks. + +## The documentation site + +This site is a private workspace package. Run it from the repository root: + +```sh +pnpm docs:site:dev # local dev server with hot reload +pnpm docs:site:build # typecheck, build, and verify the build artifacts +pnpm docs:site:preview # serve the built site +``` + +`pnpm docs:site:build` is the gate, and the `Docs` workflow runs it on every pull request and +deploys `main` to GitHub Pages. The build fails on a dead internal link, a dead anchor, a missing +image, or a page that exists in one locale but not the other — the last one is why every English +page under `guide/`, `reference/`, `examples/`, and `contributing/` has a Chinese counterpart with +the same structure. TypeScript samples stay identical across locales; prose and code comments are +translated. The generated Type API, host capability, event, notice, and diagnostics reference +pages are rendered from repository sources of truth at build time and mirrored, not translated. + +## Repository conventions + +Two rules are worth stating because they are easy to break by accident: + +- **`examples/*` are user-facing products, not fixtures.** They may use only public + `agent-bundle` exports and `workspace:*` dependencies, and they are validated at a desktop + viewport. The developer Workbench is a desktop-only application. +- **Vendored reference material under `repos/` is read-only.** Do not edit, format, or import + from it. Application code imports the published package. + +The framework's own contracts are documented in the repository next to the code: +[Framework mode](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/framework-mode.md) +for the authoring model, +[Entry conventions](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/entry-conventions.md) +for the package build, and +[Diagnostics](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/diagnostics.md) +for the `AB` code catalog. diff --git a/website/docs/en/examples/_meta.json b/website/docs/en/examples/_meta.json new file mode 100644 index 000000000..281cd0775 --- /dev/null +++ b/website/docs/en/examples/_meta.json @@ -0,0 +1 @@ +["index", "skills-starter", "hooks-and-scripts", "mcp-app", "audiobook-curator"] diff --git a/website/docs/en/examples/audiobook-curator.mdx b/website/docs/en/examples/audiobook-curator.mdx new file mode 100644 index 000000000..5fd3a2b96 --- /dev/null +++ b/website/docs/en/examples/audiobook-curator.mdx @@ -0,0 +1,115 @@ +--- +description: 'The Audiobook Curator example: a complete agent-bundle application built from route modules, request-context providers, durable state, MCP-to-CLI projection, and the framework-owned package build.' +--- + +# Audiobook Curator + +A complete plugin rather than a demonstration of one surface: a real media-management application +assembled from React Server Component route modules, request context, durable state, MCP routes, +and CLI routes. One build produces a generated stdio MCP server, an installable CLI, one Skill, +and native Claude Code and Codex plugin artifacts. It is the reference consumer of the +framework-owned package build. + +- **Run from the repository root:** `pnpm example:audiobook` +- **Package:** `@agent-bundle-example/audiobook-curator` +- **Public dependencies:** `agent-bundle` (`workspace:*`), `@agent-bundle/runtime` + (`workspace:*`), `@modelcontextprotocol/server`, `react`, `zod` +- **Targets:** `claude`, `codex`, with `marketplace: true` +- **Requires:** Node.js 22.19 or later, plus `ffprobe` and `ffmpeg` on `PATH` +- **Source:** [`examples/audiobook-curator`](https://github.com/ScriptedAlchemy/agent-bundle/tree/main/examples/audiobook-curator) + +Optional features call the foreign tools that supply their evidence: Audiobook Forge, Audiolocate +in a selected Python environment, and `whisper-cli` with a selected model. Skipping them costs +those features, not the build. This example has no hooks. + +## What it proves + +- **The route tree is the application.** `agent-bundle.config.ts` declares identity, the Node + runtime, the two targets, and the MCP-to-CLI projection. File conventions discover the rest: + 16 tool routes, one catalog resource, and one curation prompt under `src/mcp/curator/`. There + is no operation registry, no handwritten `src/mcp/curator.ts`, and no per-operation server + selector. +- **One authored surface, two protocols.** `routes.mcpCommands` projects all 16 MCP tools as + `audiobook-curator curator `, so the compiled graph carries 32 CLI commands: the 16 + authored ones under `src/cli/` plus the 16 projections. Projected tools take one optional + `--input ''`; read-only tools run directly while mutation-capable tools require + `--yes`. +- **Presentation is shared, not duplicated.** `src/components/` is one report library composed by + both the MCP routes and the rendered CLI routes, so an MCP tool and its CLI counterpart cannot + drift into two presenters. +- **Request context is observed, not assumed.** The conventional `src/providers/library.ts` + probes `ffmpeg -version` and `ffprobe -version` per request and publishes tool availability + with the probe time. The catalog resource reads it through `await agent()` and renders either + the live context or an explicit unavailable state. +- **Durable state has a mounted identity.** `src/state.ts` mounts the workspace-durable + `audiobook-curator/shelf` state with three events. If state is not mounted, the read-only MCP + tool and the rendered `shelf` CLI command both return an empty structured shelf and an explicit + unavailable notice instead of failing. +- **Suspense becomes MCP progress.** `audit_library` reports progress through the request's + `context.progress`, then places its asynchronous analysis behind React `Suspense` with an + `Agent.Progress` fallback. The generated projector streams the progress state and replaces it + with the completed analysis without changing the final structured receipt. +- **The framework owns the package build.** The config declares no `bin` or `scripts` field. The routed + `src/cli/` commands compile into `dist/bin/audiobook-curator.js` for `package.json` `bin`, and + `dist/index.js` plus declarations serve `exports`. See + [CLI and library package entries](../guide/authoring/package-entries.mdx). +- **`package.json` is the single version source.** The config declares no `plugin.version`; the + resolved version flows into project context, artifact manifests, `inspect` output, dev status, + and the `agent-bundle/meta` constant this plugin imports. + +## Rendered and plain CLI routes + +Seven authored `.tsx` commands render Agent Documents — `inventory`, `select`, `audible-search`, +`convert`, `audit`, `library-audit`, and `shelf`. An interactive terminal updates their reported +progress in place; piped output is one final Markdown document. Nine compatibility commands +remain plain `.ts` routes. + +Across both kinds, `--json` selects machine output and emits one result-schema-validated JSON +value followed by a newline. For a rendered command that value is the canonical final +`Agent.Result` value — never the Markdown presentation and never an intermediate Suspense +fallback — so a receipt consumer does not change when a command becomes rendered. + +## Working in the workspace + +```sh +pnpm --filter @agent-bundle-example/audiobook-curator build +pnpm --filter @agent-bundle-example/audiobook-curator test +pnpm --filter @agent-bundle-example/audiobook-curator test:routes +pnpm --filter @agent-bundle-example/audiobook-curator typecheck +``` + +`pnpm check` inside the package runs validate, build, typecheck, and both test pools. One +`agent-bundle build --output artifact` produces everything: complete Claude and Codex outputs +beneath `artifact/` — each host's plugin metadata, Skill, bundled CLI script, and +lifecycle-wrapped MCP server — plus the npm package beneath `dist/`. + +To exercise the built CLI without packing a tarball, link the built bin from any writable +directory already on `PATH`: + +```sh +cd examples/audiobook-curator +ln -s "$(pwd)/dist/bin/audiobook-curator.js" ~/.local/bin/audiobook-curator +audiobook-curator --help +``` + +## Running the server on stdio + +```sh +cd examples/audiobook-curator +pnpm exec agent-bundle mcp run --server curator --target claude +``` + +The command resolves the generated entry from the Claude target's MCP manifest, building a +temporary artifact first; pass `--artifact artifact` to reuse the `pnpm build` output instead. +Closing stdin exits 0 and Ctrl-C exits 130, and per-server state persists under +`.agent-bundle/mcp-run/claude/curator`. + +## Safety boundaries + +Sources are immutable and planning never mutates media. Conversion publishes to a separate +destination; metadata and chapter replacement require an explicit apply, stage into a +same-directory file, verify the staged result, and then rename atomically. JSON receipts refuse +audio suffixes and collisions with media or evidence inputs. Network bodies, process output, +traversal, and concurrency are bounded, and network work uses bounded attempts plus the caller's +cancellation signal rather than hidden deadlines. Local media processes have no wall-clock +deadline; caller cancellation and bounded stdout and stderr stay enforced. diff --git a/website/docs/en/examples/hooks-and-scripts.mdx b/website/docs/en/examples/hooks-and-scripts.mdx new file mode 100644 index 000000000..fca406688 --- /dev/null +++ b/website/docs/en/examples/hooks-and-scripts.mdx @@ -0,0 +1,89 @@ +--- +description: 'The Hooks and Scripts example: a session-start hook, two emitted scripts including a blocking exit code, durable Playground traces, and a reversible diagnostic walkthrough.' +--- + +# Hooks and Scripts + +A credential-free release-preparation session. It proves the two contracts that are easiest to +get wrong by hand: the generated hook wrapper, and the process envelope the framework puts around +an authored script. + +- **Run from the repository root:** `pnpm example:hooks` +- **Package:** `@agent-bundle-example/hooks-and-scripts` +- **Public dependencies:** `agent-bundle` (`workspace:*`) +- **Targets:** `portable`, `codex`, `claude` +- **Credentials:** none — the example reads only checked-in JSON under `release/` +- **Source:** [`examples/hooks-and-scripts`](https://github.com/ScriptedAlchemy/agent-bundle/tree/main/examples/hooks-and-scripts) + +## What it proves + +- **A hook is authored as a handler, not a host document.** `src/hooks/session-start.ts` is one + module. The build lowers it into each host's own hook document shape and emits the wrapper that + runs it. See [Hooks](../guide/authoring/hooks.mdx). +- **Both script declaration modes.** `verify-release` ships by convention — any unclaimed plain + script under `src/scripts/` is discovered — while `detect-risk` stays explicitly configured + because it restricts its targets to `portable`. The example keeps both modes covered on + purpose. +- **The framework owns the process envelope.** Both scripts export `main` and return an exit + code. Argv handling, awaiting, and exit-code adoption belong to the generated envelope, so a + nonzero return becomes a real blocking exit rather than a swallowed value. See + [Scripts and assets](../guide/authoring/scripts-assets.mdx). +- **Emitted scripts resolve their own assets.** `assets: ['release/*.json']` copies the release + manifest and risk register into every target, and each emitted script reads them relative to + its own module — never relative to the shell's working directory. +- **A failed rebuild keeps the last-good artifact.** That is the reversible walkthrough below. + +## What is authored + +| Path | What it is | +| --- | --- | +| `src/hooks/session-start.ts` | The `sessionStart` handler that directs a release session through both checks. | +| `src/scripts/verify-release.ts` | The manifest-backed packaging check, discovered by convention. | +| `src/scripts/detect-risk.ts` | The risk-register check, explicitly configured to restrict its targets. | +| `release/release-manifest.json` | The packaged release manifest, copied as an asset. | +| `release/risk-register.json` | The risk register the blocking check reads. | + +## Working in the Workbench + +1. **Overview** relates the authored hook to its emitted artifact, its exercise trace, and its + evaluation pages. Its status is the authoritative current-or-stale epoch state. +2. **Hooks** defaults to the Claude `sessionStart` binding with populated inline canonical JSON, + including `"source": "workbench"`. Run the simulation, then use **Replay saved simulation** to + rerun exactly that epoch-bound input. +3. **Playground** defaults to Script execution, the Claude target, and `verify-release`. Run it + and wait for the session to be finalized: the emitted script reads the packaged + `release/release-manifest.json` beside its own module and reports release 2.4.0 ready for + packaging. +4. Switch the target to portable and select `detect-risk`. It reads `release/risk-register.json`, + reports high-severity `REL-204`, exits with code 2, and finalizes a durable blocking trace. +5. **Logs** filters those producer records by producer, level, kind, or context; open a record to + inspect raw details. **Artifacts** is the emitted file and provenance view, while + **Comparisons** aligns outcomes only after two recorded eval runs. + +## The reversible diagnostic walkthrough + +The checked-in project is healthy, so seeing last-good behavior means breaking it on purpose. +Temporarily replace the body of `src/hooks/session-start.ts` with a syntactically incomplete +handler, press **Rebuild**, and wait for the completed **Failed** state. The Workbench reports +the new diagnostic while continuing to serve the last-good artifact. Restore the checked-in +handler, press **Rebuild** again, and wait for **Idle**: a new active epoch replaces the stale +state and clears the diagnostic. + +Do not read a Building state as a completed repair — the new active epoch is the evidence. + +## Noninteractive checks + +After the repository-level `pnpm build`: + +```sh +cd examples/hooks-and-scripts +pnpm validate +pnpm build +``` + +`pnpm check` is the same validation-and-build pair in one command. To exercise the emitted hook +wrapper without the Workbench, use the CLI's own hook surface: + +```sh +pnpm exec agent-bundle hooks list --artifact artifact +``` diff --git a/website/docs/en/examples/index.mdx b/website/docs/en/examples/index.mdx new file mode 100644 index 000000000..a33bc14b3 --- /dev/null +++ b/website/docs/en/examples/index.mdx @@ -0,0 +1,91 @@ +--- +description: 'The runnable agent-bundle examples: four walkthroughs from a Skills starter to a complete media-management plugin, plus two advanced composition references.' +--- + +# Examples + +The repository ships six examples, and they are products rather than fixtures. Each one uses +only public `agent-bundle` exports and `workspace:*` dependencies, each one builds and validates +through the same public CLI you would use, and none of them needs an API key or a signed-in host +to reach a useful state. Four have a walkthrough here: + +| Example | What it proves | Run | +| --- | --- | --- | +| [Skills Starter](./skills-starter.mdx) | Skills discovered by convention, with deterministic eval evidence. | `pnpm example:skills` | +| [Hooks and Scripts](./hooks-and-scripts.mdx) | The generated hook wrapper and script process envelope, including a blocking exit code. | `pnpm example:hooks` | +| [MCP App](./mcp-app.mdx) | A generated stdio MCP server plus an interactive MCP App resource. | `pnpm example:mcp-app` | +| [Audiobook Curator](./audiobook-curator.mdx) | A complete route-tree application: MCP, projected CLI, providers, durable state. | `pnpm example:audiobook` | + +Two more are advanced composition references, documented by their own READMEs rather than +walked through here: + +| Example | What it proves | +| --- | --- | +| [Worktree Proximity](https://github.com/ScriptedAlchemy/agent-bundle/tree/main/examples/worktree-proximity) | One root task and two child agents in linked worktrees, coordinated through durable notices. | +| [RSC Agent Runtime](https://github.com/ScriptedAlchemy/agent-bundle/tree/main/examples/rsc-agent-runtime) | An opt-in architecture experiment: one RSC runtime shared by hooks, MCP tools, and an MCP App timeline. Not a public API. | + +## Running them + +Run the `pnpm example:*` scripts from the repository root. Each one builds the workspace first, +then starts that example's foreground `agent-bundle dev` server and prints the loopback +[Workbench](../guide/development/workbench.mdx) URL. Pass `--open` (for example +`pnpm example: -- --open`) to open a browser: + +```sh +pnpm install +pnpm example:skills +``` + +For the noninteractive path, `pnpm examples:check` runs every example package's own `check` +script — validate and build, plus typecheck and tests where the example has them — with no dev +server and no browser: + +```sh +pnpm examples:check +``` + +Each example package also exposes the same public commands directly, once the repository-level +`pnpm build` has built the local `agent-bundle` workspace dependency. Every example has `validate`, +`build`, and `check`; the four walkthrough examples and Worktree Proximity also have `dev`, while the +RSC Agent Runtime demo is driven through its tests and `eval:hosts` instead: + +```sh +cd examples/skills-starter +pnpm validate # agent-bundle validate +pnpm build # agent-bundle build +pnpm check # validate and build, without opening the Workbench +pnpm dev # agent-bundle dev +``` + +## Reading an example + +Every example page below records the same four things: what the example proves, the public +packages it depends on, its root run command, and a link to its source. Read the source next to +the page — the configuration files are short on purpose, because the `src/` conventions carry +most of the structure. + +Two habits make the Workbench trustworthy while you explore: + +- **Wait for a completed state.** A rebuild is finished when it reports Idle or Failed. A + Building state is still in progress, and judging one is how a healthy project comes to look + broken. +- **Treat a failed rebuild as last-good behavior.** A failed build publishes no epoch, so the + Workbench keeps serving the previously published artifact alongside the new diagnostic. Restore + the checked-in source and rebuild; the new active epoch is the repair evidence. + +[Hooks and Scripts](./hooks-and-scripts.mdx) ships a scripted, reversible diagnostic walkthrough +for exactly that reason — break a handler, watch the failed rebuild keep the last-good artifact, +restore, and read the new epoch as the repair. [Skills Starter](./skills-starter.mdx) practices the +same rebuild-and-restore loop against stale eval evidence; the other walkthroughs only ask you to +wait for a completed state before judging a rebuild. + +## Where the concepts live + +| Example surface | Guide page | +| --- | --- | +| Skill documents and their `references/` and `assets/` | [Skills](../guide/authoring/skills.mdx) | +| Lifecycle hooks | [Hooks](../guide/authoring/hooks.mdx) | +| Scripts and asset copying | [Scripts and assets](../guide/authoring/scripts-assets.mdx) | +| MCP servers and MCP Apps | [MCP servers and MCP Apps](../guide/authoring/mcp.mdx) | +| The `bin`/`lib` package build | [CLI and library package entries](../guide/authoring/package-entries.mdx) | +| Eval suites, cases, and harnesses | [Evaluations](../guide/development/evaluations.mdx) | diff --git a/website/docs/en/examples/mcp-app.mdx b/website/docs/en/examples/mcp-app.mdx new file mode 100644 index 000000000..404c5dd4a --- /dev/null +++ b/website/docs/en/examples/mcp-app.mdx @@ -0,0 +1,107 @@ +--- +description: 'The MCP App example: one service-readiness workflow expressed as a generated stdio MCP server, a typed tool, an interactive MCP App resource, a Skill, a hook, a script, and a deterministic eval.' +--- + +# MCP App + +One service-readiness workflow, expressed once and emitted as every surface a host can reach: a +real local MCP server, a typed tool, an interactive MCP App resource, a Skill, a session-start +hook, a fixture-check script, and a deterministic eval. It is the example to read when you want +to see how the surfaces fit together instead of studying one of them alone. + +- **Run from the repository root:** `pnpm example:mcp-app` +- **Package:** `@agent-bundle-example/mcp-app` +- **Public dependencies:** `agent-bundle` (`workspace:*`), `@modelcontextprotocol/server`, + `@modelcontextprotocol/ext-apps`, `zod`; `@rstest/browser`, `@rstest/core`, + `@rstest/playwright`, and `playwright` for the browser-app test +- **Targets:** `portable`, `codex`, `claude` — the MCP App resource stays portable +- **Credentials:** none — the eval and the fixture check read only checked-in data +- **Source:** [`examples/mcp-app`](https://github.com/ScriptedAlchemy/agent-bundle/tree/main/examples/mcp-app) + +## What it proves + +- **The MCP entry convention.** `src/mcp/status.ts` default-exports the `status` server factory, + and the config declares no server `entry` — the build discovers it through the + `src/mcp/.ts` convention. See + [MCP servers and MCP Apps](../guide/authoring/mcp.mdx). +- **The generated stdio lifecycle is not yours to write.** The build wraps that factory in the + lifecycle shell: the console-to-stderr guard, signal handling, stdin-EOF exit, bounded + shutdown, and the heartbeat. +- **An MCP App is a compiled resource, not a served page.** The declared app compiles to + `mcp-apps/status.html` under the portable target with a stable `resourceUri`. Codex and Claude + keep their host artifacts but not this portable App resource. +- **Degraded evidence is first-class.** The `status` server serves immutable `compiler` and + `payments-api` health records, and `payments-api` deliberately returns degraded latency. The + interesting output is a failing check that is reported honestly, not a green screenshot. +- **A browser surface is its own proof level.** `tests/browser-app/status-panel.browser.test.ts` + mounts the production-compiled App HTML over the product bridge through + `agent-bundle/test/browser` — the `browser-app` level in + [Testing](../guide/development/testing.mdx). + +## What is authored + +| Path | What it is | +| --- | --- | +| `src/mcp/status.ts` | The `status` server factory, discovered by convention, serving the two health records. | +| `views/status-panel.ts` / `views/status-panel.html` | The MCP App entry and template compiled to `mcp-apps/status.html`. | +| `src/skills/service-readiness/` | The evidence, checks, and report needed for a service-readiness decision. | +| `src/hooks/session-start.ts` | Adds the readiness workflow to compatible host sessions. | +| `src/scripts/check-service-fixture.ts` | Validates the checked-in compiler fixture before a release walkthrough. | +| `evals/status.eval.ts` | The deterministic `mcp-app-status` suite and its `status-is-healthy` case. | + +## Working in the Workbench + +1. **Overview** opens on the Bundle dashboard. Its Author, Build, Exercise, and Evaluate stages + connect the source capability to its emitted artifact, runtime evidence, and eval result. +2. **Skills** defaults to `service-readiness`; compare its authored status policy and + readiness-report resource with the generated output and its explicit eval coverage. + **Hooks** defaults to a populated Claude `sessionStart` canonical input. +3. **Playground** defaults to Script execution, the Claude target, and `check-service-fixture`. + Run it and wait for the finalized session: the emitted checker resolves the packaged status + fixture beside its emitted module, so it succeeds independently of the shell's working + directory. +4. **Artifacts** with the portable target selected is where `mcp-apps/status.html` appears. + Before two eval runs exist, **Comparisons** deliberately shows + `At least two recorded runs are needed before a comparison can be aligned.` — the precise + empty state, not an error. +5. **MCP playground** defaults to portable and the `status` server. Open the session, list tools, + select `show-status`, choose `payments-api`, and invoke it. Invocation history shows the + degraded summary with labelled Availability and P95 latency checks, the latter failing. Open + the App preview: the rendered panel shows the same record through the MCP Apps bridge, with a + text-labelled amber `degraded` indicator. Inspect the protocol trace, use + **Restart MCP session**, then close, reset, and reopen the session to exercise the lifecycle. +6. **Evals** defaults to `mcp-app-status`. Run `status-is-healthy` and inspect the completed + passing trial attributed to `service-readiness`. + +If you edit a source file, rebuild and wait for a Failed or Idle state before judging the +result; a Building state is still in progress. + +## Noninteractive checks + +After the repository-level `pnpm build`: + +```sh +cd examples/mcp-app +pnpm validate +pnpm build +pnpm exec agent-bundle eval --case status-is-healthy --trials 1 +``` + +`pnpm check` is the validation-and-build pair without the Workbench. + +## Running the server on stdio + +```sh +pnpm exec agent-bundle mcp run --server status --target portable +``` + +`mcp run` resolves the generated entry from the portable target's MCP manifest, building a +temporary artifact first; pass `--artifact artifact` to reuse the `pnpm build` output instead. +Closing stdin exits 0 and Ctrl-C exits 130, and per-server state persists under +`.agent-bundle/mcp-run/portable/status`. + +The command loads the project-root `.env` set — including the selected `--mode` variants — by +default. Launch environment precedence is manifest env, then `.env` files, then exported operator +variables. Repeatable `--env-file ` replaces the conventional files, `--no-env` skips them, +and `--plugin-root ` is only for a copied-artifact rehearsal. The full contract is in +[Runtime environment](../reference/runtime-environment.mdx). diff --git a/website/docs/en/examples/skills-starter.mdx b/website/docs/en/examples/skills-starter.mdx new file mode 100644 index 000000000..122b77773 --- /dev/null +++ b/website/docs/en/examples/skills-starter.mdx @@ -0,0 +1,73 @@ +--- +description: 'The Skills Starter example: three engineering-operations Skills discovered by convention, with deterministic eval evidence and no credentials.' +--- + +# Skills Starter + +A Skills-only plugin. It proves that a bundle worth installing can be authored without declaring +anything but its identity and its targets — and that Skill quality can still be backed by +recorded evidence rather than a claim. + +- **Run from the repository root:** `pnpm example:skills` +- **Package:** `@agent-bundle-example/skills-starter` +- **Public dependencies:** `agent-bundle` (`workspace:*`) +- **Targets:** `portable`, `codex`, `claude` +- **Credentials:** none — both eval suites are deterministic and read checked-in fixtures +- **Source:** [`examples/skills-starter`](https://github.com/ScriptedAlchemy/agent-bundle/tree/main/examples/skills-starter) + +## What it proves + +- **Convention discovery.** `agent-bundle.config.ts` declares the plugin and its three targets + and nothing else. Every `src/skills/*/SKILL.md` directory is found by convention, so the config + never lists a Skill. That is the authoring model described in + [Skills](../guide/authoring/skills.mdx). +- **One source, three host layouts.** The same authored documents are lowered into the portable, + Codex, and Claude trees. The Workbench's Source and Generated views show whether a target + copied a document or adapted it. +- **Deterministic eval evidence.** The eval suites run through the deterministic harness against + checked-in fixtures, so a Skill's coverage is a recorded run rather than an assertion. Coverage + is labeled *indirect* because a deterministic harness cannot observe host Skill activation. + +## What is authored + +| Path | What it is | +| --- | --- | +| `src/skills/incident-triage/` | A production incident from first signal through containment, evidence collection, and a handoff-ready update. | +| `src/skills/dependency-upgrade/` | Dependency upgrade planning with API, runtime, rollout, and rollback checks. | +| `src/skills/release-review/` | The evidence, severity, workflow, and final-report requirements for an explicit release review. | +| `evals/release-readiness.eval.ts` | The `release-readiness` suite and its deterministic `release-artifact-is-ready` case. | +| `evals/engineering-operations.eval.ts` | The `engineering-operations` suite: `incident-handoff-is-actionable` and `upgrade-plan-has-rollback`. | + +Each Skill links its own `references/` checklist or runbook and a reusable `assets/` handoff or +planning template, so the Skill body stays short and the long-form material is loaded only when +it is needed. + +## Working in the Workbench + +1. **Overview** opens on the Bundle dashboard: the three Skills, the generated targets, build + health, and the next useful actions. +2. **Skills** lists `dependency-upgrade`, `incident-triage`, and `release-review`. Browse their + linked checklists and report templates, and switch between Source and Generated per target. +3. **Artifacts** defaults to the Claude target. Change the target to compare the portable, Codex, + and Claude output trees and their provenance. +4. **Evals** defaults to the `release-readiness` suite. Run `release-artifact-is-ready` and + inspect the passing trial; it consumes only the checked-in evidence fixture. +5. To practice repair, make a reversible edit to the release policy, press **Rebuild**, and wait + for a Failed or Idle result rather than a Building state. Restore the checked-in policy and + rebuild. The earlier eval is now stale for the changed build — rerun `release-readiness` to + record current, repaired evidence. + +## Noninteractive checks + +After the repository-level `pnpm build`: + +```sh +cd examples/skills-starter +pnpm validate +pnpm build +pnpm exec agent-bundle eval --case release-artifact-is-ready --trials 1 +``` + +`pnpm check` is the validation-and-build pair without the Workbench. Generated output lands in +`artifact/`, and its root contract is `artifact/agent-bundle.manifest.json`. The `.agent-bundle/` +directory holds development state and is not source material. diff --git a/website/docs/en/guide/_meta.json b/website/docs/en/guide/_meta.json new file mode 100644 index 000000000..68529fd34 --- /dev/null +++ b/website/docs/en/guide/_meta.json @@ -0,0 +1,22 @@ +[ + { + "type": "dir-section-header", + "name": "start", + "label": "Start" + }, + { + "type": "dir-section-header", + "name": "authoring", + "label": "Authoring" + }, + { + "type": "dir-section-header", + "name": "development", + "label": "Development" + }, + { + "type": "dir-section-header", + "name": "distribution", + "label": "Distribution" + } +] diff --git a/website/docs/en/guide/authoring/_meta.json b/website/docs/en/guide/authoring/_meta.json new file mode 100644 index 000000000..ae2a9e0d3 --- /dev/null +++ b/website/docs/en/guide/authoring/_meta.json @@ -0,0 +1 @@ +["index", "skills", "hooks", "mcp", "scripts-assets", "package-entries"] diff --git a/website/docs/en/guide/authoring/hooks.mdx b/website/docs/en/guide/authoring/hooks.mdx new file mode 100644 index 000000000..41de4f066 --- /dev/null +++ b/website/docs/en/guide/authoring/hooks.mdx @@ -0,0 +1,275 @@ +--- +description: 'Declare agent-bundle lifecycle hooks: the canonical events, tool selectors, the handler result contract, and hook simulation.' +--- + +# Hooks + +A hook is a handler module the compiler wraps and registers in each host's native hook document. +You declare it once, keyed by a canonical event, and the adapters translate the event name, the +matcher, and the result shape into whatever the selected host expects. + +## Declaring a hook + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + hooks: { + sessionStart: { handler: './src/hooks/session-start.ts' }, + }, + plugin: { + name: 'hooks-and-scripts', + description: 'Release preparation helpers.', + }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +Each event accepts a bare handler path, an entry object, or an array of either when one event +needs several handlers: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + hooks: { + beforeTool: [ + './src/hooks/audit.ts', + { + handler: './src/hooks/guard-writes.ts', + targets: ['claude'], + timeout: 10, + tools: ['file.write', 'shell'], + }, + ], + }, + plugin: { description: 'Guarded tooling.', name: 'guarded' }, + targets: ['portable', 'claude'], +}); +``` + +| Field | Meaning | +| --- | --- | +| `handler` | The handler module, or a `prebuilt` marker naming an already-built file inside a declared payload. | +| `targets` | Restrict the hook to specific targets. Defaults to every selected target that supports hooks. | +| `timeout` | Native hook timeout **in seconds**. Omit it to use the selected host's default. | +| `tools` | Tool selectors that scope when the hook fires. | +| `args` | Extra command arguments. Only prebuilt handlers accept arguments, and only shell-safe strings. | + +## Canonical events + +| Event | Fires when | +| --- | --- | +| `sessionStart` | A session begins. | +| `beforeTool` | Before a tool call is dispatched. | +| `afterTool` | After a tool call returns. | +| `stop` | The agent is about to stop. | +| `agentStart` | A subordinate agent starts. | +| `agentStop` | A subordinate agent stops. | +| `workspaceOpen` | A workspace is opened. Declare it as an [event route](#event-routes) (`src/events/workspace/open.tsx`), not a config hook: no target maps the plain hook, so a config-declared `workspaceOpen` is a build error on every host, including Cursor, which supports the event only through the route form. | + +A host that does not implement an event simply does not receive that hook; the emitted document +stays honest rather than inventing an equivalent. + +## Tool selectors + +`tools` accepts the canonical selectors — `shell`, `file.read`, `file.write`, `mcp`, `agent` — +each translated to the host's native matcher where the host has one. Cursor maps all five; Claude +Code has no `agent`; Codex has no `agent` and no `file.read`. Every selector on a hook must map on +every selected target, so `tools: ['file.read', 'shell']` targeting `codex` fails the build even +though `shell` alone would have worked. The [Event and hook matrix](../../reference/events.md) +renders the matcher table. `tools` also accepts explicit host-native selectors +spelled `:`, such as `claude:WebSearch` or `codex:view_image`, which +contribute only to that host's native matcher. + +A hook that selects tools must leave every selected target with at least one applicable +selector. A hook restricted to `claude:WebSearch` while also targeting `codex` fails the build +rather than emitting a Codex document with an empty matcher that would never fire. + +## The handler contract + +A handler module default-exports a function that receives the event payload and returns the +outcome. Returning nothing is the same as continuing: + +```ts +// src/hooks/session-start.ts +interface SessionStartEvent { + readonly cwd?: string; + readonly sessionId?: string; + readonly source?: string; + readonly transcriptPath?: string; +} + +export default (event: SessionStartEvent) => ({ + additionalContext: `Run verify-release from ${event.cwd ?? process.cwd()} before publishing.`, + outcome: 'continue' as const, +}); +``` + +The generated wrapper validates the result before projecting it into host-native output. Exactly +four keys are accepted, and each one is checked: + +| Key | Contract | +| --- | --- | +| `outcome` | `continue`, `deny`, or `stop`. | +| `reason` | A non-empty string, valid only when denying a `beforeTool`, `stop`, or `agentStop` hook. Denying one of those without a reason fails. | +| `additionalContext` | A string appended to the agent's context. | +| `updatedInput` | Replacement input for the pending call. | + +Per-event restrictions are enforced, not documented-and-hoped: + +- `sessionStart`, `afterTool`, and `agentStart` cannot deny, stop, or replace input. +- `beforeTool` cannot stop, and cannot replace input while denying. +- `stop` accepts only `continue`, or `deny` with a reason. +- `agentStop` cannot stop the parent flow or replace input, and on Codex it cannot add context. + +An unknown key, a wrong type, or a violation of any rule above fails the hook with a clear +message instead of being projected into a host document that would misbehave silently. + +## Prebuilt handlers + +A project that owns its own compilation can point a hook at an already-built file inside a +declared payload: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + hooks: { + afterTool: [{ + args: ['--host', 'claude'], + handler: { prebuilt: './dist/runtime/hook/index.js' }, + targets: ['claude'], + tools: ['file.write'], + }], + }, + payload: { runtime: { source: './dist/runtime', targets: ['claude'] } }, + plugin: { description: 'Self-compiled runtime.', name: 'prebuilt-plugin' }, + targets: ['claude'], +}); +``` + +A prebuilt hook emits its native command as `node "/" ` — one config +declaration replacing a hand-rolled `hooks/hooks.json` per host. Prebuilt hooks are packaged like +native hook documents: they do not compile wrappers and do not appear in the simulatable hook +index. + +## Event routes + +Config-declared hooks are the compact form. The second authoring shape is an **event route**: a +file under `src/events/` whose path is the canonical event family it handles +(`src/events/tool/before.tsx`, `src/events/stop.tsx`). It is one async default Server Component, +like an MCP tool route, plus a statically extracted `config` export: + +```tsx +// src/events/tool/after.tsx +import { Agent } from '@agent-bundle/runtime'; +import type { AgentEventRouteConfig, AgentEventRouteProps } from 'agent-bundle'; + +export const config = { + runtime: 'standalone', // 'shared' renders inside the generated MCP server process + targets: ['claude', 'codex'], + timeoutMs: 30_000, // budget inside the host's own native deadline + tools: ['file.write'], // canonical selector -> per-host native matcher +} satisfies AgentEventRouteConfig; + +export default async function AfterFileEdit({ canonical, native, signal }: AgentEventRouteProps) { + // canonical.provenance = { host, hostContractRevision, nativeEvent, source: 'native' } + return ( + + {`Recorded an edit reported by ${canonical.provenance.host}.`} + + ); +} +``` + +`canonical` is the cross-host identity the framework derives — `event`, an `idempotencyKey` +hashed from the event, target, and native payload, `observedAt`, a `sequence`, and the +`provenance` naming the host and the native event that fired. `native` is a frozen snapshot of +the validated host envelope. Nothing in `canonical` is fabricated: a host that does not report an +axis leaves it unavailable. + +The route answers through its rendered document. `Agent.Context` text becomes the host's +additional-context channel, and `Agent.Result`'s `value` may carry +`{ outcome: 'continue' | 'deny', reason?, updatedInput? }`. The projection is per event and per +host, and illegal combinations throw before anything reaches the host: `session/end`, +`compact/after`, `tool/failure`, and `workspace/open` are observation-only everywhere; a denied +`tool/before` becomes `hookSpecificOutput.permissionDecision` on Claude and Codex but +`{ permission: 'deny', … }` on Cursor; a denied `stop` becomes `{ decision: 'block', reason }` or +Cursor's `followup_message`. Which families each host supports is the generated +[Event and hook matrix](../../reference/events.md). + +Event routes reach the twenty canonical families (`session/end`, `prompt/submit`, +`compact/before`, `permission/request`, …); config-declared `hooks` cover only the seven +listed above. + +### What is on the wire + +Both shapes share the emitted `hooks/hooks.json` wiring, and both compile into a wrapper the host +invokes as `node "${CLAUDE_PLUGIN_ROOT}/hooks/.mjs"` (or the host's own root token). A +config-declared handler runs in-process inside that wrapper. An event route with +`runtime: 'shared'` instead forwards to the warm runtime living inside the generated MCP server +process, so hooks share state with tools: + +1. **Host → wrapper (stdin).** The host writes one JSON envelope. The wrapper streams stdin with a + hard 1 MiB cap, parses exactly one value, and validates it per host and per event + (`session_id`, `transcript_path`, `cwd`, tool fields, …). Any mismatch exits nonzero. +2. **Wrapper → runtime (IPC).** One newline-delimited request — `protocolVersion`, + `artifactEpoch`, `event`, `hostContractRevision`, `target`, and the validated `native` + envelope — over a per-user Unix socket (a named pipe on Windows). The socket directory is + `0700`, the socket `0600`, and the endpoint hash binds the artifact epoch, the target, and + the artifact's install directory, so two installs never share a runtime. The request is + raced against `timeoutMs` (default 5000 ms). +3. **Runtime renders.** The server rejects epoch mismatches and malformed messages, builds the + `canonical` props, and renders the route component through the react-server worker inside + the request context. +4. **Runtime → wrapper.** One JSON reply: `{ status: 'ok', output }`, or + `{ status: 'error', code }` where `code` is `epoch-mismatch`, `invalid-message`, or + `runtime-failed`. +5. **Wrapper → host (stdout).** The host-native response. On `tool/before` the wrapper always + answers with an explicit allow-or-deny decision in the host's own field names (`allow` unless + the route denied) even when the route renders no decision; silence is reserved for + observation-only families. + +Failure is closed by default. The only fallback is `fallback: 'standalone'` on a route that +also compiled its standalone form, and it fires **only** for `runtime-unavailable` (no live +socket). `runtime-timeout`, `epoch-mismatch`, `invalid-message`, and `runtime-failed` exit +nonzero — stale code never answers, and a fabricated response is never emitted. + +A route with `runtime: 'standalone'` bundles its module into the wrapper itself: the same +canonical identity, the same projection, no shared process state. `AB4817` refuses a route +that requires the shared runtime on a target where no generated MCP entry hosts it and no +standalone fallback exists. + +## Inspect and simulate + +Hooks are the surface where "it built" is least convincing, so the emitted wrapper is runnable +directly: + +```sh +npx agent-bundle inspect --root . --hooks +npx agent-bundle hooks list --artifact artifact --target claude +npx agent-bundle hooks simulate --artifact artifact --target claude \ + --hook session-start-session-start-7ab7e8a5 \ + --input '{"sessionId":"local","transcriptPath":"/tmp/transcript.jsonl","cwd":"/tmp/project","source":"startup"}' +``` + +`--hook` must be the exact `name` (or `id`) that `hooks list` printed — normalization derives it +as `--`, so a bare event name such as `session-start` matches nothing. +`session-start-session-start-7ab7e8a5` is the name the declaration at the top of this page +produces; the hash covers the event, handler path, targets, tools, timeout, and args, so any +change to the declaration yields a new name, and the one to copy is always the one `hooks list` +prints for your build. +The payload is the **canonical** input; the wrapper converts it to the host's native envelope and +validates it as the host would, so a Claude `sessionStart` simulation needs `sessionId`, +`transcriptPath`, `cwd`, and `source` (tool events additionally need `toolName`, `toolInput`, and +`toolUseId`). + +`hooks simulate` runs the real emitted wrapper — the same file the host will execute — so the +result you see is the result the host would get. The developer Workbench exposes the same +playground with the raw stdout, stderr, and outcome for each run. + +## Next + +- [MCP servers and Apps](./mcp.mdx) — the other executable surface. +- [Scripts and assets](./scripts-assets.mdx) — code a hook or Skill can call. diff --git a/website/docs/en/guide/authoring/index.mdx b/website/docs/en/guide/authoring/index.mdx new file mode 100644 index 000000000..d9265895b --- /dev/null +++ b/website/docs/en/guide/authoring/index.mdx @@ -0,0 +1,145 @@ +--- +description: 'The agent-bundle configuration model: defineConfig, every top-level field, and the boundary between config and conventions.' +--- + +# Configuration model + +`agent-bundle.config.ts` at the project root is the whole declarative surface. It is one small +flat object holding project identity, target selection, and the policy no route file can own. +Everything structural — which Skills exist, which MCP routes exist, which scripts ship — comes +from the `src/` conventions unless you override it here. + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { description: 'Evidence-backed project tools.', name: 'my-plugin' }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +`defineConfig` is an identity helper: it exists to type the object, not to transform it. + +## Project identity + +`plugin` names the plugin as hosts will see it: + +| Field | Meaning | +| --- | --- | +| `name` | The host-native plugin slug. Never the npm package name. | +| `description` | One sentence shown by hosts and written into generated manifests. | +| `logo` | Project-relative path to an image copied into host artifacts that support it. | +| `version` | **Deprecated.** The release version belongs in `package.json`. | + +`package.json` is authoritative for release identity. A declared `plugin.version` that disagrees +with it reports the `AB4008` warning, and a release build with no version anywhere is refused +outright (`AB4013`) rather than packaging the development fallback. Plugin code reads its own +identity from `agent-bundle/meta` instead of a hand-maintained version module. + +## Targets + +`targets` selects the artifact layouts the build emits: + +| Target | What it emits | +| --- | --- | +| `plugin` | One multi-host bundle with `.claude-plugin/`, `.codex-plugin/`, and `.cursor-plugin/` manifests over shared component directories. | +| `claude` | The Claude Code plugin layout. | +| `codex` | The Codex plugin layout. | +| `cursor` | The Cursor plugin layout. | +| `portable` | The [Agent Plugins open standard](https://agent-plugins.org) (specification 1.0.0). Cursor, Codex, VS Code, GitHub Copilot, Kiro, and ChatGPT are native clients. | + +The portable standard packages only Skills and MCP servers, so rules, commands, and hooks are +honestly unavailable on that target rather than silently dropped. Claude Code consumes the +standard only through CLI translation, which is why its dedicated target remains necessary. + +## The full surface + +| Field | Owns | +| --- | --- | +| `plugin` | Project identity. Required. | +| `targets` | Which artifact layouts to emit. | +| `skills` | Explicit Skill paths or globs, replacing the `src/skills/*` convention. See [Skills](./skills.mdx). | +| `hooks` | Lifecycle hook handlers, keyed by canonical event. See [Hooks](./hooks.mdx). | +| `mcp` | MCP server declarations and their MCP Apps. See [MCP servers and Apps](./mcp.mdx). | +| `scripts` | Stable output names mapped to script entries. See [Scripts and assets](./scripts-assets.mdx). | +| `assets` | Explicit asset entries replacing the root `assets/` convention. | +| `bin` / `lib` | The npm package build. See [Package entries](./package-entries.mdx). | +| `routes` | Power-tier route-graph policy: `routes.servers` per-server modes, `routes.cli`, and `routes.mcpCommands` projection. | +| `output` | `distPath` — where the artifact root lives. | +| `runtime` | `node` — the minimum Node.js version for generated executables. | +| `payload` | Prebuilt directory trees packaged byte-for-byte. | +| `state` | `false` to disable the `src/state.ts` convention. | +| `marketplace` | `true` adds Cursor's optional `.cursor-plugin/marketplace.json`. Claude Code and Codex always emit their local marketplace manifests regardless. | +| `evals` | Eval suite discovery (`include`), the run directory (`runsDir`), and the optional `semanticGrader`. See [Evaluations](../development/evaluations.mdx). | +| `dev` | Development-only settings that never reach an artifact, such as `agentApi`. | +| `tools` | The single bundler escape hatch. | + +Host-scoped extension keys — `claude`, `codex`, `cursor`, `portable` — are contributed by the target +adapters through declaration merging, so a host-specific value stays in its own adapter instead +of leaking into compiler core. Ordinary projects need no extension key at all. + +## Host-scoped declarations + +Every host key is optional and every field inside it is optional. `claude` and `codex` extend +the shared `AgentBundleHostConfig`, whose single field `nativeHooks` names a project-authored, +target-native hook document (`hooks.json`) the adapter validates and merges beside the compiled +hooks. The adapter-owned fields are: + +| Key | Fields | Where they land | +| --- | --- | --- | +| `claude` (`ClaudeHostConfig`) | `bin`, `channels`, `defaultEnabled`, `dependencies`, `displayName`, `lspServers`, `marketplace`, `metadata`, `monitors`, `outputStyles`, `settings`, `themes`, `userConfig`, `workflows` | `bin`, `outputStyles`, and `workflows` are project directories copied to the plugin-root conventions of the same name; `channels`, `dependencies`, `defaultEnabled`, `displayName`, `metadata`, and `userConfig` are validated into `.claude-plugin/plugin.json` (`channels` must name servers in the emitted `.mcp.json`); `marketplace` enriches the generated marketplace entry; `monitors` and `themes` are experimental and emit `monitors/monitors.json` and one `themes/.json` per key; `lspServers` and `settings` are described below. | +| `codex` (`CodexHostConfig`) | `apps`, `author`, `homepage`, `interface`, `keywords`, `license`, `marketplace`, `repository` | `author`, `homepage`, `keywords`, `license`, and `repository` are publisher metadata in `.codex-plugin/plugin.json`; `interface` (brand color, category, capabilities, composer icon, default prompts, developer name, …) is merged over the compiler's generated install-surface defaults; `apps` maps registered MCP connection ids into the root `.app.json` compatibility document; `marketplace` authors the emitted `.agents/plugins/marketplace.json` entry — `category` (defaults to the interface category), `displayName` (defaults to the plugin name), and `policy` with `installation` (`AVAILABLE`, `INSTALLED_BY_DEFAULT`, `NOT_AVAILABLE`) and `authentication` (`ON_INSTALL`, `ON_USE`); the source always stays the local plugin root, and any other key is an error rather than a silently shipped field. | +| `cursor` (`CursorHostConfig`) | `author`, `category`, `homepage`, `keywords`, `license`, `minClientVersions`, `publisher`, `repository`, `tags` | Manifest metadata layered onto the generated `.cursor-plugin/plugin.json`, every field admitted by the pinned Cursor plugin schema. `author` is a closed `{ name, email? }` object (no `url`); `minClientVersions` maps client identifiers to strict `X.Y.Z` semver strings, e.g. `{ cursor: '3.13.0' }`, and anything else is an error. Cursor documents no `nativeHooks` surface, so `cursor` does not extend `AgentBundleHostConfig`. | +| `portable` ([`PortableManifestConfig`](../../api/interfaces/index.PortableManifestConfig.md)) | `author`, `extensions`, `homepage`, `keywords`, `license`, `repository` | Agent Plugins 1.0.0 §5.4 metadata layered onto the emitted root `plugin.json`; `extensions` maps reverse-domain client namespaces to opaque object payloads (§8). Omitted fields are omitted from the manifest. | + +Everything else about a Cursor plugin is derived from the cross-host model. The host capability +tables in the [host reference](../../reference/hosts.md) record which of these surfaces each pinned +host version actually honours. + +Two Claude Code surfaces deserve a closer look because their contracts are narrower than their +names suggest: + +- **`claude.lspServers`** — emitted as a plugin-root `.lsp.json` by the `claude` target and the + Claude half of `plugin`. Path tokens expand only in `command`, `args`, `env`, and + `workspaceFolder`. agent-bundle does not include the language-server binary; install it + separately so the declared command is available on `PATH`. Codex, Cursor, and the portable + format do not receive this configuration. +- **`claude.settings`** — emitted as a plugin-root `settings.json` that Claude Code applies when + the plugin is enabled. The pinned contract supports only `agent` and `subagentStatusLine`, and + any other key is rejected rather than shipped as a default Claude Code would silently ignore. + No path tokens are expanded here, because `settings.json` is absent from the host's + placeholder-substitution table. Declaring `agent` also raises a warning while the plugin + `agents/` component is deferred: the referenced agent has to reach the plugin root some other + way, such as a prebuilt payload. + +## Runtime floor + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { description: 'Evidence-backed project tools.', name: 'my-plugin' }, + runtime: { node: '24.0' }, + targets: ['portable'], +}); +``` + +Generated executables target Node.js 22.12 or newer by default. `runtime.node` raises that floor +— it can never be lowered — and the selected floor is recorded as `runtime.node` in the artifact +manifest. + +## JSX means rendering + +Structure lives in config and conventions; JSX appears only where something is rendered. An +executable route is one async default Server Component that does the work and returns `Agent.*` +nodes, and it calls `await agent()` only when it needs host, session, actor, workspace, +capability, or state context. There is no public `execute`/`render` split, and a plain `.ts` +route is never wrapped in React behavior. + +## Authorable surfaces + +- [Skills](./skills.mdx) — Markdown Skills, their resources, and rendered Skill sources. +- [Hooks](./hooks.mdx) — the seven canonical lifecycle events and tool selectors. +- [MCP servers and Apps](./mcp.mdx) — generated route servers, handwritten stdio entries, and browser Apps. +- [Scripts and assets](./scripts-assets.mdx) — artifact scripts and static files. +- [Package entries](./package-entries.mdx) — `bin`, `lib`, the routed CLI, and the bundler escape hatch. diff --git a/website/docs/en/guide/authoring/mcp.mdx b/website/docs/en/guide/authoring/mcp.mdx new file mode 100644 index 000000000..172626dc7 --- /dev/null +++ b/website/docs/en/guide/authoring/mcp.mdx @@ -0,0 +1,310 @@ +--- +description: 'Author MCP servers in agent-bundle as generated route modules or handwritten stdio entries, plus MCP Apps and the plugin-root environment anchor.' +--- + +# MCP servers and MCP Apps + +An MCP server is the executable half of a plugin. agent-bundle offers two ways to author one: +**generated route modules**, where a file path is a tool's identity, and **handwritten stdio +entries**, where you construct the server yourself and the framework owns only its process +lifecycle. + +## Generated route servers + +Put one module per route under `src/mcp//`: + +```text +src/mcp/curator/ +├── tools/status.tsx +├── resources/catalog.tsx +├── prompts/triage.tsx +└── apps/status-panel.tsx +``` + +The path supplies identity: `src/mcp/curator/tools/status.tsx` is the `status` tool of the +`curator` server. No declaration is needed for the server to exist. + +Each executable module exports static `config`, its schemas, and one async default Server +Component: + +```tsx +// src/mcp/curator/tools/status.tsx +import React from 'react'; +import type { ToolConfig, ToolRouteProps } from 'agent-bundle'; +import { Agent, agent } from '@agent-bundle/runtime'; +import { z } from 'zod'; + +export const config = { + annotations: { readOnlyHint: true }, + description: 'Read runtime status.', +} satisfies ToolConfig; +export const inputSchema = z.object({ verbose: z.boolean().optional() }).strict(); +export const resultSchema = z.object({ status: z.literal('ready') }).strict(); + +export default async function Status({ input, signal }: ToolRouteProps) { + if (signal.aborted) throw new DOMException('aborted', 'AbortError'); + if (input.verbose) await agent(); + const result = { status: 'ready' as const }; + return ( + + Runtime is ready. + + ); +} +``` + +The compiler statically reads `config`, imports schemas and implementations only into generated +entries, installs `runAgentRequest`, and derives the real MCP server from the route graph. Each +call renders through a warm internal Flight dispatcher and lowers the final Agent Document to +legal MCP output. Flight is an implementation transport inside the generated runtime — never a +public host wire protocol, and raw Flight bytes never cross the MCP wire. + +`ToolConfig` and `ToolRouteProps` are public types: + +```ts twoslash +import type { ToolConfig } from 'agent-bundle'; + +export const config = { + annotations: { readOnlyHint: true }, + description: 'Read runtime status.', +} satisfies ToolConfig; +``` + +Call `await agent()` only when the route needs context. The handle exposes the invocation plus +the `host`, `session`, `actor`, and `workspace` identity axes. Each axis is *observed*: a +transport publishes an `available` value and its source when it knows one, or `unavailable` with +a typed reason when it does not. Bare stdio supplies neither a session id nor HTTP actor +authentication, so those axes stay honestly unavailable rather than being fabricated. + +## Shared layouts + +`src/layout.tsx` is the composition point around every rendered route — the `layout.tsx` idea from +page frameworks applied to Agent Documents. It default-exports one component receiving +`{ children, route, signal }` and renders `Agent.Result` around `children`, the route's rendered +element. `src/mcp//layout.tsx` nests inside it for one generated server; composition order +is root layout, server layout, route. Generated MCP tools, resources, and prompts, rendered +`src/cli/**` commands, projected MCP commands, and rendered `src/scripts/*.tsx` are all wrapped. +Event routes are host protocol responses and browser App routes are browser builds, so neither is. + +```tsx +// src/layout.tsx — the whole layout a consumer writes +import { Agent, type AgentLayoutProps } from '@agent-bundle/runtime'; +import React from 'react'; + +export default function Layout({ children, route }: AgentLayoutProps) { + return ( + + {children} + + ); +} +``` + +An `Agent.Result` without a `value` is a container: the runtime merges it with the route's own +`` while decoding, so the route keeps its result value, +`structuredContent`, and content, and the layout adds only the shared shell — a heading, a trailing +`Agent.Context` note, document metadata. `metadata` objects merge key by key with the container +winning; because the MCP projector exposes root metadata as the result's `_meta`, a layout that +declares metadata changes `_meta`, and one that does not leaves it untouched. `route` is the +compile-time identity (`id`, `kind`, `name`, `serverId` for MCP kinds), `signal` the request abort +signal, and `await agent()` works inside a layout exactly as in a route. + +The route's element resolves **before** the layout chain renders, so a throwing route still fails +the whole render (CLI exit `1`, MCP transport failure) rather than being downgraded to a boundary +error beneath the layout's shell; the trade-off is that a layout cannot stream a `Suspense` +fallback around `children`. A layout whose default export is not a function, or that exports the +route-only `config`/`inputSchema`/`resultSchema`, is `AB4830`; `.ts` and `.tsx` siblings for one +scope are `AB4831`; a server layout whose server declares no tool, resource, or prompt routes is +`AB4832`, while a server pinned to `custom`, `command`, or `remote` skips its layout entirely. The +route-unit and projection test levels compose the same chain, so `renderRoute('tool:...')` and +`invokeMcpTool(...)` prove the composed document; a module passed directly to `renderRoute()` +composes no layout. + +## Handwritten stdio entries + +A server declared in config with no `entry`, `command`, or `url` picks up the conventional +`src/mcp/.ts` module. The framework serves it under the lifecycle shell when it +**default-exports a server factory**: + +```ts +// src/mcp/curator.ts — the whole stdio entry a consumer writes +import { createRscMcpServer } from '@agent-bundle/runtime/plugin'; + +import { application } from '../application.js'; + +export default () => createRscMcpServer(application, 'curator'); +``` + +The generated shell provides, in order: console-to-stderr redirection *before* the consumer +module evaluates, the factory call, raw `process.stdout.write` restored for protocol frames, +transport construction and connect, SIGINT to exit 130, SIGTERM to exit 143, stdin EOF to exit 0 +so the client can respawn, transport-close to exit 0, a five-second bounded shutdown race +against wedged transports, and heartbeat and activity logging on stderr (five-minute interval, +sixty-second activity throttle, labeled with the server name). + +That guard matters because stdout carries JSON-RPC framing: one stray `console.log` from any +imported module would corrupt the protocol stream. + +Self-connecting entries — modules that construct and connect a transport at top level without a +default export — keep their existing behavior byte for byte. Source validation reports the +informational `AB4730` nudge suggesting the factory upgrade; it is never an error. + +The same lifecycle is public API for hand-rolled entries: + +```ts +import { redirectConsoleToStderr, runStdioServer } from 'agent-bundle/mcp-entry'; +``` + +## Declaring servers in config + +Declare a server when you need something the convention cannot express — a different entry path, +a target restriction, extra environment, or a command or remote server you do not compile: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + mcp: { + servers: { + curator: { entry: './src/mcp/curator.ts', transport: 'stdio' }, + remote: { transport: 'streamable-http', url: 'https://example.com/mcp' }, + }, + }, + plugin: { description: 'Library curation tools.', name: 'curator' }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +| Field | Meaning | +| --- | --- | +| `entry` | A source module to compile, or a `prebuilt` marker naming an already-built file inside a declared payload. | +| `command` / `args` / `cwd` | An external process to launch instead of a compiled entry. | +| `url` / `headers` | A remote server reached over `streamable-http`. | +| `transport` | `stdio` or `streamable-http`. | +| `env` | Extra environment for stdio servers. | +| `targets` | Restrict the server to specific targets. | +| `apps` | Browser MCP Apps registered on this server. | + +## The plugin-root environment anchor + +Every emitted stdio MCP server entry carries an `AGENT_BUNDLE_PLUGIN_ROOT` environment variable +holding the plugin install root in the target's native spelling: `${CLAUDE_PLUGIN_ROOT}` on +Claude Code, `${PLUGIN_ROOT}` on portable, `${CURSOR_PLUGIN_ROOT}` on Cursor, and `./` on Codex, +resolved against the entry's plugin-root `cwd`. Codex has no path-token interpolation, so a Codex +stdio server without a plugin-root working directory omits the anchor; source-built (`entry:`) +servers always have one on every target. + +**Resolve persistent state and bundled assets against this anchor, not the process working +directory.** Claude Code currently launches stdio servers from the host's own working directory +and ignores any stdio `cwd` field (its placeholder table excludes `cwd`). The Claude adapter +therefore **omits** `cwd` when the working directory is the canonical plugin root — the standard +source-built `entry:` case — and instead makes the entry path absolute by prefixing the first +argument with `${CLAUDE_PLUGIN_ROOT}/`, alongside the environment anchor. That canonical +plugin-root `cwd` is the one token-bearing value Claude accepts; any other `cwd` that carries a +path token is rejected, and only a non-token, explicitly authored `cwd` is passed through verbatim. + +A server's own `env` entries win over the injected value, so declaring +`env: { AGENT_BUNDLE_PLUGIN_ROOT: ... }` replaces the anchor. The variable name is exported so +consumer code never has to hardcode it: + +```ts twoslash +import { pluginRootEnvAnchor } from 'agent-bundle'; + +const readPluginRoot = (env: Record): string | undefined => + env[pluginRootEnvAnchor]; +``` + +## MCP Apps + +An MCP App is a browser surface compiled to self-contained HTML and registered as a resource on +the generated server. The conventional location is `src/mcp//apps/*.{ts,tsx}`, where a +static `config.resourceUri` is required. Prefix the file with `_` to opt out. + +Declaring an App in config gives it an explicit HTML template and target restriction: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + mcp: { + servers: { + status: { + apps: { + status: { + entry: './views/status-panel.ts', + resourceUri: 'ui://mcp-app-example/status.html', + targets: ['portable'], + template: './views/status-panel.html', + }, + }, + }, + }, + }, + plugin: { description: 'A service-readiness assistant.', name: 'mcp-app-example' }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +Compiled Apps are available to server code through `agent-bundle/mcp-apps`, which the compiler +replaces for local MCP servers. Importing it outside an agent-bundle compilation throws rather +than returning an empty registry — an unsupported boundary made explicit instead of failing +silently at run time. + +An App declared on a *prebuilt* server stays a development surface: the Workbench compiles it +live, and the build assumes the payload already serves the resource. + +## Server modes + +`routes.servers.` opts a server out of route generation when the directory convention +should not apply — `custom` for a handwritten server, `command` for an external process, and +`remote` for a URL-reached server. The full mode contract, including collision recovery, is in +[Entry conventions](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/entry-conventions.md). + +## Run and inspect + +```sh +npx agent-bundle inspect --root . --routes +npx agent-bundle mcp list --artifact artifact --target claude --server curator +npx agent-bundle mcp invoke --artifact artifact --target claude --server curator \ + --tool status --input '{"verbose":true}' +npx agent-bundle mcp run --artifact artifact --target claude --server curator +``` + +`mcp run` executes one built stdio server in the foreground: it resolves the generated entry +(named with a digest of the server name) from the target's MCP manifest, expands path tokens through the target adapter, +loads the project-root `.env` set, and forwards the child's exit code. Without `--artifact`, a +temporary artifact is built first. + +## Live host MCP proxy + +During development, a host can keep one stdio MCP process connected while `agent-bundle dev` +rebuilds the generated server behind it. Configure the host's MCP server command as: + +```json +{ + "command": "agent-bundle", + "args": [ + "dev", + "proxy", + "--root", + "/absolute/path/to/plugin", + "--server", + "tools" + ] +} +``` + +The proxy discovers the loopback server through the project's development lock and connects to +the stable Streamable HTTP endpoint at `/mcp/host/`. `--target` defaults to +`portable`, and `--url` overrides discovery. Successful rebuilds keep the stdio connection open, +route new calls to the active epoch, let admitted calls finish against their original epoch, and +forward MCP catalog change notifications. If the epoch or development server disappears, the +proxy fails closed with an MCP error and an `AB8024` or `AB8025` diagnostic. + +The endpoint is intentionally unauthenticated because the development server binds only to +loopback and is never exposed beyond the local machine. + +## Next + +- [Scripts and assets](./scripts-assets.mdx) — the non-protocol executables in an artifact. +- [Package entries](./package-entries.mdx) — `bin`, `lib`, and the routed CLI. diff --git a/website/docs/en/guide/authoring/package-entries.mdx b/website/docs/en/guide/authoring/package-entries.mdx new file mode 100644 index 000000000..602ffe3d5 --- /dev/null +++ b/website/docs/en/guide/authoring/package-entries.mdx @@ -0,0 +1,260 @@ +--- +description: 'The framework-owned npm package build: bin and lib entries, the executable envelope, the routed CLI, agent-bundle/meta, and the single bundler escape hatch.' +--- + +# CLI and library package entries + +agent-bundle is the build product for agent plugins the way Rslib is for libraries: one +`agent-bundle.config.ts`, one CLI, framework-owned entry lifecycles, and a single blessed escape +hatch into the bundler. The same config that emits host artifacts also owns the npm package +build, so a plugin that also ships as a CLI or a library needs no second bundler config. + +## `bin` and `lib` + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + bin: { 'my-plugin': './src/cli.ts' }, + lib: { dts: true, entry: './src/index.ts' }, + output: { distPath: 'artifact' }, + plugin: { description: 'A CLI plus a plugin.', name: 'my-plugin' }, + targets: ['portable', 'claude'], +}); +``` + +| Config | Output | Notes | +| --- | --- | --- | +| `bin: { '': './src/cli.ts' }` | `dist/bin/.js` | Self-executing ESM bundle, `#!/usr/bin/env node` shebang, executable bit. | +| `lib: { entry: './src/index.ts', dts: true }` | `dist/.js` plus `dist/**/*.d.ts` | Single-entry ESM profile, node target, es2022 syntax. | + +The conventions `src/cli.ts` and `src/index.ts` fill these in when the config is silent. Config +always wins, and `bin: false` / `lib: false` opt out. + +Because package outputs live in `dist/`, host artifacts must live somewhere else: the CLI's +default artifact root is `artifact/`, and pointing `output.distPath` or `--output` at `dist` on a +project with package entries is `AB4706`. `dist` is a mandatory-ignored directory: package outputs never enter project source +snapshots or Skill and asset discovery. + +Outputs are staged and published atomically, and their provenance — bytes, SHA-256, and sorted +project-relative source inputs — is reported on the build result exactly like artifact files. + +### Declarations + +`lib.dts` defaults to `true`. Declaration generation resolves `typescript` from the project, so +add it as a devDependency. It compiles the lib entry's source directory as its own program: +compiler options come from the project `tsconfig.json` via `extends`, `rootDir` is pinned to the +entry's directory, and only that subtree is included — test files never fail or pollute the +package build. Declarations land flat under `dist/`, one `.d.ts` per source module. + +The `lib` profile is deliberately thin. A package that needs a multi-format matrix (UMD, multiple +entries, per-format tsconfig) has outgrown the profile and genuinely wants Rslib. That is the one +case where a second bundler config remains, by choice. + +## The executable envelope + +A `bin` entry — or an artifact script — whose module exports `main`, or default-exports a +function, receives the generated process envelope: + +```ts +// src/cli.ts — the whole CLI entry a consumer writes +export const main = async (argv: readonly string[]): Promise => { + // ... + return 0; +}; +``` + +The envelope awaits `main(process.argv.slice(2))`, adopts a numeric return as the process exit +code, and lets an escaped rejection surface through Node's top-level failure path (stack to +stderr, exit code 1). Self-executing modules with no `main` export bundle directly, byte for +byte. + +## The routed CLI + +A `src/cli/**` surface compiles into one framework-generated executable instead of a +hand-written dispatcher, superseding the `src/cli.ts` bin convention for the project. Nesting is +identity: `src/cli/library/audit.ts` runs as ` library audit`. + +```ts twoslash +// src/cli/inspect.ts — the whole command a consumer writes +import type { CliRouteConfig, CliRouteProps } from 'agent-bundle'; +import { z } from 'zod'; + +export const config = { + description: 'Inspect a bounded source tree without changing it.', + positionals: ['root'], +} satisfies CliRouteConfig; + +export const inputSchema = z.object({ + maxFiles: z.number().int().min(1).max(256).optional(), + root: z.string().min(1), +}).strict(); + +export const resultSchema = z.object({ scanned: z.number().int() }).strict(); + +export default async function inspect({ input, signal }: CliRouteProps) { + signal.throwIfAborted(); + return { scanned: input.maxFiles ?? 0 }; +} +``` + +The compiler statically projects `inputSchema` onto argv, generates nested help (`--help` at +every level, `--version` at the root), and emits `dist/bin/.js` through the same +bundler synthesis as every other bin. At run time the shell resolves the command path, parses and +coerces argv, validates through the module's own schemas, executes the default function inside +the typed Agent request context, writes one canonical JSON line to stdout, and maps exit codes +deterministically: + +| Exit code | Meaning | +| --- | --- | +| `0` | Success, or the result's `exitCode` under `config.exitCode: 'result'`. | +| `1` | Execution failure. | +| `2` | Usage or input failure. | +| `130` / `143` | SIGINT / SIGTERM, which also reach the route's `AbortSignal`. | + +A `.tsx` command route swaps the default function for an async default Server Component with the +same props and renders through the runtime dispatcher against a sibling +`dist/bin/-flight.mjs` worker. It gains the four output modes described in +[Scripts and assets](./scripts-assets.mdx). Routed CLI projects need `@agent-bundle/runtime` as a +dependency, because the generated executable installs the request context through it. + +Opt out with `bin: false`, `routes.cli: 'conventional'`, or by prefixing a path segment with `_`. + +### The routed CLI inside host artifacts + +The package bin only reaches users who install the npm package, while hooks, Skills, and scripts +ship with the host artifact. So the build also emits the same compiled command graph into every +selected target as `/bin/.mjs` (plus `bin/-flight.mjs` when any +command renders). Every built-in target publishes the `cli` capability that admits it. The +artifact bin is a self-contained ESM module with no shebang or executable bit — run it as +`node /bin/.mjs `, exactly like `scripts/*.mjs`. Help, argv +parsing, output modes, exit codes, and signals match the package bin; the one difference is that +workspace-durable state without a host-supplied `AGENT_BUNDLE_PLUGIN_ROOT` anchors on the artifact +root (the parent of `bin/`, the same fallback the generated MCP worker uses) instead of +`$PWD/.agent-bundle/state`, so a co-installed CLI and server share one store. + +Reach it from the other surfaces with the plugin-root token — `${CLAUDE_PLUGIN_ROOT}/bin/.mjs` +in Claude Skill Markdown and hook commands, `${PLUGIN_ROOT}/…` in Codex hooks, `${CURSOR_PLUGIN_ROOT}/…` +in Cursor hooks — or, from a compiled script, as the sibling +`new URL('../bin/.mjs', import.meta.url)`. `inspect` accounts for the bin as one `cli` +component per target and the artifact manifest records both files. A target without the `cli` +capability omits the bin and reports `AB4765`; a host-emitted file at the same path (a `claude.bin` +directory shipping `.mjs`) is `AB4766`. The package build's `dist/bin/.js` +is unchanged. + +### Projecting MCP tools into the CLI + +`routes.mcpCommands` adds tools from generated MCP servers to the same command graph and +executable, including in projects with no `src/cli/**` routes at all. `true` selects every +eligible tool; the object form takes `include` and `exclude` patterns matching the +`:` identity, with `*` as the only wildcard. + +Each projected tool runs as ` ` with the protocol tool name preserved +verbatim. Its only input option is `--input` taking one JSON object. A tool is read-only only +when its static MCP annotations explicitly set `readOnlyHint: true`; every other tool is +mutation-capable and fails closed unless `--yes` is present. Every declared pattern must match at +least one eligible tool, and a misspelling fails with `AB4822` listing the available identities. + +## Release identity + +Plugin code reads its own identity from the framework instead of maintaining a hand-written +version module: + +```ts +import meta, { name, packageName, packageVersion, version } from 'agent-bundle/meta'; +``` + +`version` is the resolved plugin version, `name` is the host-native plugin slug — never the npm +package name — and `packageName` / `packageVersion` are the validated npm axes, `undefined` for +an unpackaged development project. The compiler replaces the specifier in every compiled surface: +artifact scripts, the routed CLI, MCP entries, hook wrappers, the package build, and browser MCP +App bundles. It is a reserved specifier, so the `tools` hatch cannot externalize it and no +emitted bundle can carry an unresolved import of it. + +Outside an agent-bundle compilation the published module throws rather than reporting a +fabricated identity, and a release build refuses a project with no release version at all +(`AB4013`). + +## Packaging and installers + +When package outputs and at least one Claude, Codex, or Cursor host pack are built inside the +project, the framework also emits one self-contained package-relative installer at +`dist/bin/.js` — or `-install.js` when that name is taken, with a +numeric suffix if both are. Declare the matching `package.json` `bin` value. Its grammar is +`install [--scope ] [--json]`, help lists only built hosts, and it resolves the +shipped artifact directory from `import.meta.url` rather than the caller's working directory, so +it works from `node_modules` regardless of the current directory. No npm lifecycle performs an +installation. + +```sh +npx agent-bundle prepack --root . --output artifact --json +``` + +`prepack` runs the release build and `npm pack --dry-run --json --ignore-scripts`, then gates the +exact package and artifact inventory, manifest hashes, package bin targets, and release-version +agreement. Use it as an npm `prepack` script; `--ignore-scripts` prevents recursion. + +## Prebuilt payloads + +Some projects legitimately own their compilation — a coordinated multi-environment bundler +topology the per-entry hatch cannot express — but still want framework-owned host packaging. The +top-level `payload` block names already-built directory trees the build packages byte-for-byte at +stable paths, and `entry: { prebuilt: './dist/…' }` or `handler: { prebuilt: './dist/…' }` points +the generated host manifests at files inside them without compiling them. + +Every payload file keeps its exact relative path, because the framework did not compile these +files and cannot rewrite the references inside them. Integrity stays content-addressed anyway: +each payload file lands in the artifact manifest with its SHA-256 and the `prebuilt` file kind, +and hashes into the project revision. Run your own build first — a missing or empty payload is a +warning under `dev` so a clean checkout works, but `agent-bundle build` refuses it. + +## The bundler escape hatch + +`tools.rsbuild` (an Rsbuild environment-config fragment) and `tools.rspack` (an Rspack config +object, mutator function, or array) merge **last** into every bundler config agent-bundle +synthesizes: artifact scripts, MCP entries, hook wrappers, MCP App views, and the package build. +This is why a consumer never needs a second bundler config file. + +The hatch is bounded. The framework invariant hook runs *after* your `tools.rspack`, and the +resolved-config assertions still run after the merge. A value that breaks an artifact contract — +async chunks, output roots, self-containment — fails the build with a hard diagnostic instead of +silently overriding the contract. Reserved module specifiers are protected the same way: a hatch +that externalizes `agent-bundle/mcp-entry`, `agent-bundle/meta`, or `agent-bundle/mcp-apps` fails +the build, at config inspection for statically visible `externals` and through a post-build scan +for function-form ones. The hatch customizes *how code compiles*, never *what the artifact +promises*. + +One dual-engine caveat: artifact scripts, MCP entries, hook wrappers, and the package build +compile through Rslib and run under the bundler versions nested inside `@rslib/core`, while MCP +App views compile through the workspace-pinned `@rsbuild/core`. A class imported from a +separately installed `@rspack/core` therefore has a different identity than whichever engine +executes the config. Never construct plugins or run `instanceof` checks against an imported +`@rspack/core` — use the utils argument passed to mutator functions instead: + +```ts +tools: { + rspack: (config, { rspack }) => { + // `rspack` is always the executing engine's own object. + return config; + }, +}, +``` + +To see exactly what the hatch produced: + +```sh +npx agent-bundle inspect --bundler --root . +npx agent-bundle inspect --bundler --root . --target claude --json +``` + +That dumps the synthesized configuration for every output the build composes, exactly as the +build lowers it, using the same functions the build uses — so the dump cannot drift from what +compiles. Entries the framework wraps also carry the generated wrapper module source. + +The full contract for everything on this page is in +[Entry conventions](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/entry-conventions.md). + +## Next + +- [Configuration model](./index.mdx) — the rest of the config surface. +- [Project structure](../start/project-structure.mdx) — where each of these entries lives. diff --git a/website/docs/en/guide/authoring/scripts-assets.mdx b/website/docs/en/guide/authoring/scripts-assets.mdx new file mode 100644 index 000000000..9f56101be --- /dev/null +++ b/website/docs/en/guide/authoring/scripts-assets.mdx @@ -0,0 +1,125 @@ +--- +description: 'Ship executable scripts and static assets in every agent-bundle artifact, including rendered .tsx scripts and the four CLI output modes.' +--- + +# Scripts and assets + +Scripts are the executables an artifact carries that are not MCP servers: the thing a Skill tells +the agent to run, or the check a hook wants performed. Assets are the static files that ship +beside them. + +## Scripts + +Top-level `scripts` is a record of stable output names to an entry path, or to an object with +`entry` and an optional `targets` restriction: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { + name: 'hooks-and-scripts', + description: 'Release preparation helpers.', + }, + scripts: { + 'check-service-fixture': './src/scripts/check-service-fixture.ts', + 'detect-risk': { entry: './src/scripts/detect-risk.ts', targets: ['portable'] }, + }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +The key is the **output name**, so it stays stable even when the source file moves. Compilation +depends on the entry's extension: + +| Entry | Output | +| --- | --- | +| JavaScript / TypeScript | Bundled to `scripts/.mjs` in every selected target artifact. | +| `.sh`, `.bash`, `.py` | Copied byte-for-byte, preserving source file modes. | + +### The convention + +An unclaimed plain module at `src/scripts/.ts` ships through the same pipeline with no +declaration at all. A `scripts` entry that references the file claims it, which is how the +example above keeps `detect-risk` explicit only because it restricts targets. + +Nested modules under `src/scripts/` are a hard error (`AB4808`) — the output name must be +unambiguous. Opt out by prefixing a path segment with `_`, or by claiming the file with an +explicit `scripts` entry. + +Every explicit config entry that references a module — `scripts`, `hooks`, `mcp`, `lib` — claims +it out of convention, with one exception: a `bin` entry does **not** claim a direct +`src/scripts/.ts` child. The bin compiles to `dist/bin/.js`, disjoint from every +artifact, and both envelopes run the same `main`, so the module ships as the npm bin *and* the +artifact `scripts/.mjs`. Such a module must export `main` or be self-executing: a +`default`-only plain script is `AB4738`, and a rendered `.tsx` script must export both its default +component and `main` (`AB4737`). For a bin-only module, prefix a path segment with `_`. + +### Rendered scripts + +`src/scripts/.tsx` is a rendered script. Its async default component receives `argv` and +`signal` and renders through the Agent renderer with the full CLI output contract. It compiles to +`scripts/.mjs` plus a `scripts/-flight.mjs` react-server worker. + +The extension is the explicit, visible contract. A plain `.ts` script is never wrapped in React +behavior, and an explicit `scripts` config entry stays plain regardless of extension. Rename to +`.ts` to opt out. + +Rendered scripts and rendered routed-CLI commands share one output contract: + +| Mode | Behavior | +| --- | --- | +| Interactive TTY | Progress updates in place; the final document prints as Markdown. | +| Piped | Exactly one final Markdown document, with no partial fallbacks. | +| `--json` | The canonical validated final value. | +| `--ndjson` | The sequence-numbered render-event stream. | + +`--ndjson` is an agent-bundle CLI and script output dialect, not MCP JSON-RPC, and it is never +written as non-MCP bytes to an MCP server's stdout. Diagnostics stay on stderr; machine output +owns stdout. + +### Running a script + +`script.run` is a production-mounted, trusted-local Workbench Playground operation. It runs only +the selected manifest-owned emitted script for the selected target, inside a managed workspace, +and preserves bounded stdout and stderr, the exit code, cancellation, and raw event references. +It cannot be handed a browser-supplied command. + +## Assets + +Files under a root `assets/` directory copy byte-for-byte into every target artifact's `assets/` +directory. That convention needs no configuration. + +A top-level `assets` list **replaces** that convention with explicit entries — literal file +paths, whole directories, or globs, all resolved from the project root: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + assets: ['release/*.json', 'evals/fixtures/status/result.json'], + plugin: { + name: 'hooks-and-scripts', + description: 'Release preparation helpers.', + }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +Entries outside `assets/` keep their project-relative path under the artifact's `assets/` +directory, so `release/notes.json` lands at `assets/release/notes.json`. + +## Integrity + +The generated `agent-bundle.manifest.json` records a SHA-256 digest for every emitted file, +including copied scripts and assets. Artifact validation is therefore content-addressed: it +compares real bytes against the manifest rather than checking that a path exists. + +```sh +npx agent-bundle validate --artifact artifact --strict +``` + +## Next + +- [Package entries](./package-entries.mdx) — `bin`, `lib`, the routed CLI, and the bundler escape hatch. +- [Hooks](./hooks.mdx) — the lifecycle handlers that often invoke these scripts. diff --git a/website/docs/en/guide/authoring/skills.mdx b/website/docs/en/guide/authoring/skills.mdx new file mode 100644 index 000000000..6c462cd3d --- /dev/null +++ b/website/docs/en/guide/authoring/skills.mdx @@ -0,0 +1,169 @@ +--- +description: 'Author agent-bundle Skills as SKILL.md documents with resources, host extensions, canonical path tokens, or rendered SKILL.tsx sources.' +--- + +# Skills + +A Skill is a Markdown document plus the files it references. agent-bundle discovers Skills from +the `src/` convention, lowers each one into the spelling every selected host expects, and ships +the directory's other files as that Skill's resources. + +## The convention + +One directory per Skill, with `SKILL.md` inside: + +```text +src/skills/release-review/ +├── SKILL.md +├── assets/report-template.md +└── references/checklist.md +``` + +That ships with no declaration at all. Everything in the directory other than `SKILL.md` — and +other than the rendered-skill source files, which are build inputs — becomes a resource of that +Skill, copied into the artifact with its relative path preserved. + +## The document + +`SKILL.md` starts with YAML frontmatter and continues as ordinary Markdown: + +```md +--- +name: release-review +description: Reviews release evidence and issues an auditable readiness verdict. +--- +# Release review + +## When to use + +Use this Skill when a release candidate needs a go/no-go verdict supported by +checked, reproducible evidence. + +## Required resources + +- Read [the release checklist](references/checklist.md) to inspect the artifact. +- Deliver the result with [the report template](assets/report-template.md). +``` + +Frontmatter is required. A document with none reports `AB3001`; invalid YAML reports `AB3002`. +`name` and `description` are what every host reads — the description is the activation surface, +so write it as the condition under which the Skill should be used, not as a title. + +Links to sibling files are how a Skill points at its own resources. They stay relative in the +emitted document, so they resolve inside whichever artifact the host installed. + +## Selecting Skills explicitly + +Conventional discovery covers `src/skills/*/SKILL.{md,ts,tsx}`. Declare `skills` when you need a +different location, a subset, or a literal list: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { description: 'Engineering operations.', name: 'ops-bundle' }, + skills: ['src/skills/*'], + targets: ['portable', 'codex', 'claude'], +}); +``` + +Literal paths stay literal; globs match Skill directories or `SKILL.md` files. Config always +wins over the convention. + +## Host extensions + +Portable Skill metadata — `name`, `description`, `license`, `compatibility`, `allowed-tools` (the +Agent Skills spelling; camelCase `allowedTools` is accepted only under `targets.claude`), and a +free-form `metadata` record — is understood by every target. Host-specific keys live under +their host so they reach only that adapter: + +| Host | Examples | +| --- | --- | +| `claude` | `allowedTools`, `disallowedTools`, `argumentHint`, `model`, `effort`, `context: 'fork'`, `background`, `userInvocable`, `disableModelInvocation`, `whenToUse`, `shell`, `paths`, `hooks` | +| `codex` | `dependencies.tools`, `interface` (display name, icons, brand color, default prompt), `policy.allowImplicitInvocation` | +| `cursor` | `globs`, `icon`, `color`, `paths`, `disableModelInvocation` | + +`targets` in Skill frontmatter is that per-host object and nothing else; a list such as +`targets: ['claude']` is `AB3006`. A Skill is emitted to every target the project selects — there +is no per-Skill artifact restriction. + +## Path tokens + +Host placeholder syntax differs — `${CLAUDE_PLUGIN_ROOT}` is not what Cursor or the portable +standard write. Author the canonical token instead and let build-time lowering substitute the +host spelling: + +| Token | Means | +| --- | --- | +| `agent-bundle:path:plugin-root` | The plugin install root. | +| `agent-bundle:path:plugin-data` | The host-provided plugin data directory. | +| `agent-bundle:path:workspace-root` | The user's workspace or project root. | +| `agent-bundle:token:arguments` | The invocation arguments. | +| `agent-bundle:token:session-identity` | The current session identity. | +| `agent-bundle:token:skill-root` | The installed Skill's own directory. | + +Lowering substitutes syntax only; no runtime value is resolved at build time. A token a host +cannot express is reported rather than silently emitted as literal text. + +## Rendered Skills + +When a Skill document is generated rather than typed — the same checklist repeated per +environment, a table derived from a contract — put a component at `src/skills//SKILL.tsx` +(or `.ts`). It default-exports a component and exports a `frontmatter` record; the build compiles +the rendered tree into the `SKILL.md` document hosts consume: + +```tsx +// src/skills/release-review/SKILL.tsx +import { Skill } from 'agent-bundle'; + +export const frontmatter = { + name: 'release-review', + description: 'Reviews release evidence and issues an auditable readiness verdict.', +}; + +export default () => ( + <> +

Release review

+

Evidence lives under {Skill.PluginRoot()}/assets.

+ +); +``` + +Six `Skill.*` members emit canonical tokens: `Skill.Arguments`, `Skill.PluginData`, +`Skill.PluginRoot`, `Skill.ProjectRoot`, `Skill.SessionIdentity`, and `Skill.SkillRoot`. +`Skill.Resource` renders a Markdown link (`[path](path)`), not a token. Host syntax is +applied during lowering, never in the component. + +A hand-authored `SKILL.md` in the same directory always wins — an authored file beats a +generated one — and the shadowed component reports the informational `AB4735` nudge. A rendered +module that fails to load reports `AB3003`; one that does not default-export a component +function, or does not export a `frontmatter` record, reports `AB3004`. + +`defineSkill` types a Skill definition next to a rendered source: + +```ts twoslash +import { defineSkill } from 'agent-bundle'; + +export const skill = defineSkill({ + description: 'Reviews release evidence and issues an auditable readiness verdict.', + name: 'release-review', +}); +``` + +## Inspecting what shipped + +```sh +npx agent-bundle inspect --root . --skills +``` + +The skill focus shows each discovered Skill, its provenance (`conventional` or `config`), its +resources, and the per-target lowering decisions. In the developer Workbench, the Skills page +renders the emitted document for each host. + +Raw HTML, JSX/MDX, and Mermaid inside Skill Markdown are inert in the Workbench renderer. That +is a deliberate containment boundary, not a rendering gap. + +## Next + +- [Hooks](./hooks.mdx) — react to lifecycle events. +- [MCP servers and Apps](./mcp.mdx) — expose tools, resources, and prompts. diff --git a/website/docs/en/guide/development/_meta.json b/website/docs/en/guide/development/_meta.json new file mode 100644 index 000000000..019782edf --- /dev/null +++ b/website/docs/en/guide/development/_meta.json @@ -0,0 +1 @@ +["index", "workbench", "testing", "evaluations"] diff --git a/website/docs/en/guide/development/evaluations.mdx b/website/docs/en/guide/development/evaluations.mdx new file mode 100644 index 000000000..a2f9d1386 --- /dev/null +++ b/website/docs/en/guide/development/evaluations.mdx @@ -0,0 +1,155 @@ +--- +description: 'Run agent-bundle eval suites: pass/fail/inconclusive semantics, minimum evidence, smoke versus reliability, comparison alignment, and the authenticated native harnesses.' +--- + +# Evaluations + +Tests prove that a route renders and that a generated server honours its wire contract. Evals ask +a different question: given a prompt, does the agent actually reach your plugin, and is the +outcome the one you claimed? That answer is probabilistic, so the eval runner is built to report +honestly rather than to look green. + +## Suites are typed modules + +Eval suites are discovered by convention from `evals/**/*.eval.ts`, and each one default-exports +`defineEvalSuite`: + +```ts twoslash +import { defineEvalSuite, expectOutcome, expectSkillActivation } from 'agent-bundle/eval'; + +export default defineEvalSuite({ + cases: [{ + assertions: [ + expectSkillActivation({ skill: 'service-readiness' }), + expectOutcome({ script: './graders/status-result.ts' }), + ], + fixture: './fixtures/status', + hosts: { portable: { model: 'deterministic' } }, + id: 'status-is-healthy', + invocation: { mode: 'explicit', skill: 'service-readiness' }, + prompt: 'Use service-readiness to verify the checked-in compiler service fixture.', + trials: 1, + }], + name: 'mcp-app-status', +}); +``` + +A case names its `id`, `prompt`, `fixture`, `hosts` (with a pinned model per host), `invocation` +mode — `automatic`, `explicit`, or `none` — its `assertions`, and how many `trials` to run. Case +ids must be unique within a suite. Suite discovery, the runs directory, and the optional semantic +grader are configured under the top-level `evals` config block; see +[Configuration](../../reference/configuration.mdx). + +```sh +npx agent-bundle eval --root . --suite mcp-app-status --trials 3 +npx agent-bundle eval compare +``` + +## Three outcomes, not two + +Every assertion resolves to `pass`, `fail`, or **`inconclusive`**, and declares the minimum +evidence it accepts. An assertion that needs stronger evidence than the harness produced is +inconclusive — never silently passed. + +| Evidence | Meaning | +| --- | --- | +| `observed` | The harness saw the thing happen in the host's own output. | +| `inferred` | The harness derived it from surrounding evidence rather than observing it. | +| `unavailable` | No evidence at all. Never a valid declared minimum, because it would accept absence. | + +Assertion helpers default their `minimumEvidence` to the strongest bar their evidence can +support: `expectExitCode`, `expectMcpCall`, `expectNoMcpCall`, and `expectOutcome` default to +`observed`, while `expectSkillActivation` and `expectNoSkillActivation` default to `inferred` +because not every host publishes an activation event. Raise a default with +`minimumEvidence: 'observed'` when you want the weaker evidence to read as inconclusive instead +of as a pass. + +Because an inconclusive trial produced no evidence, it cannot report success either: +`agent-bundle eval` exits nonzero when a run has any failing **or** any inconclusive trial. + +## Smoke evidence versus a reliability number + +Fewer than **three** trials is reported as smoke evidence rather than a reliability number. One +green trial is a signal that the path exists; it is not a rate. + +Comparison rows are only aligned when every one of these facets matches: case, fixture, semantic +grader identity, harness, host CLI version, invocation, and model. The artifact target digest is +deliberately *not* an alignment facet — it is the thing under comparison. Facets a run did not +record are labeled unverified rather than assumed aligned, and an unmatched facet makes the row +non-comparable instead of producing a delta nobody should trust. + +## The optional semantic grader + +A semantic grader is configured with exactly one pinned Claude model: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + evals: { + semanticGrader: { harness: 'claude', model: 'claude-sonnet-4-5' }, + }, + plugin: { description: 'Evidence-backed project tools.', name: 'my-plugin' }, + targets: ['portable', 'claude'], +}); +``` + +It runs only with `agent-bundle eval --harness claude`, and only for Claude-pinned cases. After +the primary trace is usable and the deterministic graders finish, Agent Bundle makes **one** +server-owned, plugin-free Claude grading call. Its fixed result id is `claude-semantic`, and its +request, raw stream, stderr, and canonical provenance are retained with the trial artifacts. A +malformed or failed semantic grader leaves the trial inconclusive rather than becoming plugin +evidence — a grader that broke is not a plugin that misbehaved. + +Semantic grading requires a native Claude harness and a signed-in Claude Code session. +Deterministic and Codex selections are refused when it is configured. + +## The Eval page + +The Workbench Eval page admits a selected run, reports live progress, and can cancel it through +the run lifecycle. Each trial exposes its persisted raw evidence when present, plus recorded CLI, +invocation, grader, and usage provenance. + +Comparison cells show recorded provenance and usage and only include aligned case, fixture, +harness, invocation, host and model, CLI, and grader facets; unmatched facets are labeled +non-comparable or unverified. Trial duration is persisted; provider token usage is shown only +when the native stream reported it. + +Playground can promote selected durable outcome and assertion evidence from a trace into a draft +eval case — see [Developer Workbench](./workbench.mdx). + +## Native harnesses + +The deterministic harness needs nothing installed. The native Claude and Codex harnesses run the +real host CLI, and the rules there are strict: + +- This package never accepts, requests, injects, or persists a model-provider API key. +- Native harnesses use an **already installed, signed-in** CLI, after provider-key environment + variables are removed. +- A missing, incompatible, or unauthenticated CLI is reported as a *harness* failure, distinct + from a plugin failure. +- Native smokes are opt-in and are not part of an ordinary test run. +- Codex exposes no authoritative Skill-activation event, so Codex activation evidence is + `inferred` and is never reported as `observed`. + +For a manual authenticated local smoke, complete each CLI's normal interactive sign-in first, +then verify a supported non-prerelease CLI. Claude Code must be at least `2.1.232`; Codex must be +at least `0.147.0`: + +```sh +claude --version +npx agent-bundle eval --harness claude --trials 1 + +codex --version +npx agent-bundle eval --harness codex --trials 1 +``` + +Each Codex trial sets a temporary `CODEX_HOME` and copies only the installed CLI's opaque +`auth.json` into it. Your normal Codex home, configuration, and installed-plugin state are not +used as trial state and are left unchanged. The full credential contract is in +[Security](../../reference/security.mdx). + +## Next + +- [Distribution](../distribution/index.mdx) — turning a validated artifact into an installable bundle. +- [Limitations](../../reference/limitations.mdx) — what evals and harnesses currently do not prove. diff --git a/website/docs/en/guide/development/index.mdx b/website/docs/en/guide/development/index.mdx new file mode 100644 index 000000000..58215588b --- /dev/null +++ b/website/docs/en/guide/development/index.mdx @@ -0,0 +1,59 @@ +--- +description: 'The agent-bundle development loop: the dev server, rebuild epochs, and the three surfaces that turn a build into evidence.' +--- + +# The development loop + +Development has one entry point and three evidence surfaces. The entry point is +`agent-bundle dev` — a foreground, loopback-only server that rebuilds the artifact as inputs +change. The surfaces are the developer Workbench, the test harness, and the eval runner. + +```sh +npx agent-bundle dev --root . +``` + +## The rebuild loop + +`dev` prepares project source, builds an artifact, and publishes it as an **epoch**: an immutable +generation identified by an `epochId`. Every surface that reads a built artifact — the Workbench +artifact tree, an MCP session, a hook simulation, a development host install — names the epoch it +is reading, so a rebuild mid-session can never silently change what a result was evidence about. + +Rebuilds are debounced and serialized. A successful build publishes an `artifact.available` +event; a **failed** build publishes nothing, so the previously published epoch stays exactly as +it was. There is no partially published generation. + +The same serialized pass also rebuilds the `dist/` package build when the project declares +`bin`/`lib` entries. That build has its own provenance-based incremental boundary: after a +success, the sorted source inputs of every emitted file are kept, and the next rebuild is skipped +unless one of those inputs was invalidated, the configuration file, `package.json`, or +`tsconfig.json` changed, the normalized `bin`/`lib` declaration or the `tools` hatch changed, the +invalidation was manual or initial, or the previous package build failed. A brand-new file that +changes module resolution without touching a tracked input is picked up on the next tracked +change rather than instantly. + +A package build failure never invalidates the artifact epoch that already committed. It surfaces +as one `AB7103` **warning** on the succeeded build attempt and retries on the next invalidation. + +Development also publishes generated route declarations at `.agent-bundle/routes.d.ts` from the +same compiled graph. Each write goes to a sibling temporary file and is renamed over the prior +complete declaration atomically, so invalid source keeps the last-good file, and a successful +route-free preparation removes it. + +## The three surfaces + +| Surface | What it is | What it proves | +| --- | --- | --- | +| [Developer Workbench](./workbench.mdx) | The loopback UI `dev` serves: diagnostics, Skills, artifact provenance, MCP and hook playgrounds, Playground traces, eval runs. | Nothing by itself — it is where you *look at* and *exercise* real generated output. | +| [Testing](./testing.mdx) | `agent-bundle/rstest` and `agent-bundle/test`, plus the framework-owned contract matrix. | Recorded proof levels, from a route module rendering to a bundle spawned from an installed host layout. | +| [Evaluations](./evaluations.mdx) | Typed eval suites run through deterministic, Claude, or Codex harnesses. | Whether an agent actually reaches your plugin, with `pass` / `fail` / `inconclusive` and a declared minimum evidence bar. | + +A plugin that builds is not a plugin that works, and each of these answers a different question. +None of them substitutes for another. + +## Where this section stops + +Authoring surfaces — what a Skill, hook, MCP route, script, or package entry *is* — are in +[Authoring](../authoring/index.mdx). Turning a validated artifact into something a host can +install is in [Distribution](../distribution/index.mdx). Exact flags, config field semantics, and +runtime contracts are in [Reference](../../reference/index.mdx). diff --git a/website/docs/en/guide/development/testing.mdx b/website/docs/en/guide/development/testing.mdx new file mode 100644 index 000000000..5db4685ac --- /dev/null +++ b/website/docs/en/guide/development/testing.mdx @@ -0,0 +1,234 @@ +--- +description: 'Test agent-bundle routes through the framework harness: the rstest configuration helper, renderRoute, the nine proof levels, and the generated-plugin contract matrix.' +--- + +# Testing + +Route modules are tested through the framework, not through a hand-written bundler +configuration. Two subpaths ship that harness and both are opt-in: `@rstest/core` and `react` are +optional peer dependencies, so a project that never tests routes installs neither. Rendering also +needs `@agent-bundle/runtime`, which the project already owns whenever it has route modules — the +generated entries import it the same way. + +## The configuration helper + +`agent-bundle/rstest` compiles the project once — the same route-graph compilation the build +performs, with no artifact build — and returns a plain Rstest configuration object carrying the +test manifest, the route loaders, React's `react-server` resolution, and the automatic JSX +runtime: + +```ts +// rstest.route-unit.config.ts +import { defineConfig } from '@rstest/core'; +import { agentBundleRstest } from 'agent-bundle/rstest'; + +export default defineConfig(await agentBundleRstest()); +``` + +Route-unit tests default to `tests/route-unit/**/*.test.{ts,tsx}` and need **their own** Rstest +run, because rendering a route requires Node's `react-server` condition for the whole worker +process. Keep them out of the project's ordinary `rstest` run. + +## Rendering a route + +`agent-bundle/test` holds the helpers. `renderRoute` executes a route — by compiled route id, or +by importing the module directly — through the real renderer and the real request store, and +resolves to the final Agent Document: + +```ts twoslash +import { expectDocument, renderRoute } from 'agent-bundle/test'; + +export const summarizes = async (): Promise => { + const { document } = await renderRoute('tool:library/summarize', { + input: { title: 'Dune' }, + }); + + expectDocument(document) + .toHaveStatus('success') + .toContainMarkdown('Dune') + .toHaveValue({ chapters: 24 }); +}; +``` + +`renderRoute` accepts `input`, `args` (CLI routes), request-`context` overrides — including a +`context.progress` reporter — render `limits`, and a `signal`. It returns the document, the +request-scoped progress the route reported, the resolved provenance, and the route's own +`resultSchema`-parsed value. Progress is recorded whether or not the caller supplies a reporter +of its own. + +`testManifest()` exposes the compiled route inventory, so a suite can iterate every route in +process rather than paying for a build per route. Every failure — an unknown route, a refused +route kind, a rejected input, a render error — names the route id, the target kind, and the +module provenance. + +Matchers over the Agent Document contracts: `toHaveStatus`, `toContainMarkdown`, `toContainText`, +`toHaveValue`, `toHaveError`, and `toHaveNodeKinds`. + +This is the route-unit proof level, and only that: it proves a route module renders to the +document it claims. It is not evidence about the MCP transport, a packed artifact, or a browser +surface. + +## Proof levels + +The levels are separate on purpose. Each helper stamps the level it carried into its provenance +and prints it in every failure, because a pass at one level is never a receipt for another. + +| Level | Helpers | What it proves | +| --- | --- | --- | +| `route-unit` | `renderRoute`, `renderRouteEvents` | A route module renders to the document — and render-event stream — it claims. | +| `mcp-in-memory` | `openInMemoryMcpServer`, `invokeMcpTool`, `readMcpResource`, `getMcpPrompt`, `listMcpSurface`, `runContractMatrix` | The real generated MCP server's protocol contract, over the SDK's in-memory transport. | +| `dev-epoch` | `runDevEpochContractMatrix` | An epoch-pinned generated stdio process opened through the Workbench session service; the caller owns the epoch lease and process lifetime, and MCP App routes are covered (surface plus `ui://` sweep). | +| `cli-dispatch` | `invokeCli`, `cliJson`, `cliNdjson` | A plain or rendered argv vector resolved and run through the routed CLI's own shell — including rendered Markdown, explicit TTY, JSON, and NDJSON modes — in-process. | +| `packed-stdio` | `openPackedMcpServer`, `runPackedContractMatrix` | A built artifact's generated entry running as a real process over stdio. | +| `packed-deleted-source` | `removeProjectSource`, `openPackedMcpServer({ deletedSource })`, `runPackedContractMatrix` | The packed stdio process still runs after project source and configuration are removed and verified absent. | +| `host-install` | `openInstalledHostMcpServer`, `runInstalledHostContractMatrix` | A built bundle staged into an isolated host root, discovered in the emitted host format, and spawned from the installed layout. | + +Two further levels sit alongside these seven, for nine in all. `agent-bundle/test/browser` supplies `mountBrowserApp` +for the browser-safe `browser-app` level — production-compiled MCP App HTML mounted over the +product bridge in a real browser page — and `simulated` reuses the installed-host helper +`openInstalledHostMcpServer` without `sessionEvidence`: an emitted bundle staged directly into +an isolated host-shaped root and spawned without a host-owned install, which is weaker than +`host-install`. + +```ts twoslash +import { cliJson, cliNdjson, invokeCli, invokeMcpTool } from 'agent-bundle/test'; + +export const proofs = async (): Promise> => { + // mcp-in-memory: the generated server projects the document to protocol content. + const call = await invokeMcpTool('summarize', { input: { title: 'Dune' } }); + + // cli-dispatch, plain .ts route: resolve argv, execute, and map the exit code. + const run = await invokeCli(['library', 'audit', './books', '--max-files', '8']); + + // cli-dispatch, rendered .tsx route: exercise the shell's rendered output modes. + const rendered = await invokeCli(['library', 'report', './books', '--ndjson']); + const events = cliNdjson(rendered); + + // An explicit TTY proves the in-place progress path rather than the piped one. + const tty = await invokeCli(['library', 'report', './books'], { tty: true }); + + return { + exitCode: run.exitCode, + finalEvent: events.at(-1)?.type, + inPlaceProgress: tty.stdout.includes('\r\u001B[2K'), + scanned: cliJson(run), + structured: call.structuredContent, + }; +}; +``` + +`expectEvents` asserts over a render-event stream. `toContainSequence` is sequence-tolerant — an +extra `progress` or `replace` frame is legal and cannot turn a passing render red — while a +missing frame, a reordering, or a regressed ordinal still fails. `toHaveMonotonicSequence`, +`toCompleteOnce`, `toHaveProgress`, and `toHaveNoErrors` cover the rest of the contract. + +### Process evidence is deliberately expensive + +Among the packed levels, only `packed-stdio` and its strictly stronger `packed-deleted-source` +upgrade are process evidence: pack once, install once, build once, remove and verify source once, +spawn once, and iterate every per-route assertion inside that one session. `dev-epoch` is process +evidence of a different shape — the Workbench's own epoch-pinned generated stdio process, not a +packed artifact — so a `dev-epoch` pass says nothing about what a pack ships. The deleted-source journey also reads +the embedded MCP App resource from the generated server; it does not prove native-host install or +dispatch, or an install mode that copies the artifact elsewhere. + +`host-install` is separate installed-layout process evidence. Its deterministic +adapter-simulator lane is unconditional, available Claude and Codex binaries also prove their +public install paths, and Cursor records its unavailable non-interactive host-session surface +explicitly. + +## The contract matrix + +The contract matrix is the framework-owned generated-plugin wire-contract suite. Three entry +points share one implementation; boundary differences are explicit capability flags, not forked +check logic. The project supplies only fixtures — valid inputs, a declared `resultCompat` policy +for every in-memory tool route, optional `previousResults` payloads, optional `cancellation` +cases, and an optional deterministic lifecycle transition driver with declarative expectations. + +**`runContractMatrix` (`mcp-in-memory`)** opens one real MCP client against the real generated +server over the SDK's in-memory transport and runs the full matrix. It proves wire-surface +completeness against the compiler manifest, fixture coverage, successful-path invocation sweeps, +JSON serialized round-trip through each tool route's own `resultSchema`, declared additive or +closed compat behavior on serialized payloads, acceptance of previous-server payloads under the +current schema, rejection of negative inputs derived from the advertised `listTools` input JSON +Schema, and mid-flight cancellation hygiene. In-memory transport may pass structured values +without serialization, so the matrix closes that gap with an explicit +`JSON.parse(JSON.stringify(...))` round-trip before validation. MCP Apps are reported as +not-applicable for surface registration, because the in-memory level does not register them. + +**`runPackedContractMatrix` (`packed-stdio` / `packed-deleted-source`)** runs against an +already-open packed session — the single packed journey owns session open and close. It proves +process stdio evidence for surface completeness (including compiled MCP App resource URIs in +`listResources`), fixture coverage, successful-path sweeps, advertised input-schema rejection, and +client-side cancellation hygiene. It cannot load project route modules, because source may be +deleted and verified absent, so serialized-round-trip, compat-probe, and version-skew checks — +including their per-lifecycle-phase variants — are reported `not-applicable` with an honest +reason. The packed server validates every tool result through its bundled `resultSchema` before +returning; a successful sweep invocation is that evidence. + +**`runInstalledHostContractMatrix` (`host-install`)** runs against an already-open session from +`openInstalledHostMcpServer`. The opener reads the host's emitted MCP document from the installed +root, verifies the manifest, the component, resource, and hook paths, and the artifact file +digests, spawns that installed command, and observes the running version from the live MCP +`initialize` result. Its report records source, built-artifact, installed-artifact, and +running-process versions separately, and fails closed when any value is missing or differs. +Metadata records the host binary version when observed, the adapter revision, the manifest and +schema digest, and the framework version. Module-backed checks remain honestly not-applicable, +because loading project modules would cross back into the source and build tree. + +```ts twoslash +import { runContractMatrix } from 'agent-bundle/test'; + +export const matrix = async (): Promise => { + await runContractMatrix({ + fixtures: { + 'tool:library/summarize': { + input: { title: 'Dune' }, + previousResults: [{ chapters: 24 }], + resultCompat: 'additive', + }, + }, + }); +}; +``` + +The packed and installed-host entry points take the same fixture shape plus the session they run +against: `runPackedContractMatrix` needs the open packed session and the manifest compiled +*before* source removal, and `runInstalledHostContractMatrix` needs the session +`openInstalledHostMcpServer` returned along with that manifest. + +### Lifecycle fixtures + +Lifecycle fixtures replay `unknown → queued → running → first-progress → repeated-progress → +terminal` over the matrix's one open client. The framework validates every phase's structured +content and rendered output, additive and closed compatibility, live progress before settlement, +journal accumulation, declared notices, idempotent commit replay, and typed budget rejection. + +A caller-supplied same-store `restart` callback adds durability evidence at that boundary; +without one the check is honestly `not-applicable`. Packed callers should wire that callback into +the existing packed journey's restart rather than creating a second pack, build, and install +path. A lifecycle fixture's optional `state.catalog` assertion pins its declared id and lifetime +to the compiler manifest used by that same mounted-state replay. + +### Event routes and runtime identity + +When the compiled manifest contains event routes, the packed and installed-host boundaries sample +the read-only event-runtime status before and throughout sequential matrix events. The +`runtime-instance-identity` check fails if the warm `instanceId` changes, the artifact epoch +drifts, or availability degrades to `runtime-restarted` or `runtime-unavailable`. In-memory runs +and compiled artifacts without event routes report runtime identity as honestly +`not-applicable`. + +No matrix boundary proves browser App HTML or artifact-rebuild replay. + +When the advertised input schema declares `additionalProperties: false`, plain `z.object` tool +routes may still strip unknown keys without a protocol failure. The negative-inputs check records +that tolerance when other generated negatives still prove rejection paths. + +A failing matrix throws one aggregated `AgentTestError` with code `contract-violation`, naming +every failing route, every failing check, and the proof-level label the run actually carried. + +## Next + +- [Evaluations](./evaluations.mdx) — the surface that asks whether an agent reaches the plugin at all. +- [Artifact validation](../distribution/validation.mdx) — the checks that gate a built bundle. diff --git a/website/docs/en/guide/development/workbench.mdx b/website/docs/en/guide/development/workbench.mdx new file mode 100644 index 000000000..2e8e2161b --- /dev/null +++ b/website/docs/en/guide/development/workbench.mdx @@ -0,0 +1,183 @@ +--- +description: 'The agent-bundle developer Workbench: its loopback boundaries, epoch-pinned MCP sessions, Playground traces, development host installs, and the optional Agent API.' +--- + +# Developer Workbench + +`agent-bundle dev` serves a prebuilt developer Workbench over loopback. It is the place to look +at what the compiler actually emitted — per host, per epoch — and to run the emitted wrappers +instead of trusting that a green build implies a working plugin. + +```sh +npx agent-bundle dev --root . +npx agent-bundle dev --root . --port 3100 --no-open +``` + +## Boundaries + +These are contracts, not defaults: + +- **Loopback only.** The server binds to loopback and is never exposed beyond the local machine. +- **A foreground development session, not a hosted service.** Closing the process ends the + session. +- **Epoch-pinned reads.** Nothing follows a new build implicitly; a surface that reads an + artifact names the epoch it read. +- **Trusted-local operations only.** The browser never supplies a command, a working directory, a + native model, or a credential. See [Security](../../reference/security.mdx). + +## What it shows + +| Page | Contents | +| --- | --- | +| Overview | Project identity, normalized model, and diagnostics. | +| Skills | Every Skill document, including each host's lowered output. | +| Artifacts | The artifact tree with provenance and epoch comparison. | +| MCP | An artifact-bound playground with the raw protocol trace. | +| Hooks | A playground that runs the emitted hook wrapper. | +| Playground | A durable, ordered trace with replay and export. | +| Evals | Eval runs and run comparisons. | +| Logs | Concise events plus raw stdout, stderr, and protocol streams, grouped by producer: normalization, build, diagnostics, MCP, hook, host trial, and grader. | + +## MCP sessions bind to an epoch + +A Workbench MCP session binds `{ epochId, target, serverName }` when it is opened and never moves +to a new epoch automatically. That is what makes a protocol trace meaningful: every frame in it +came from one generated server built from one set of inputs. + +- **Restart MCP session** respawns that generated server on its *selected* epoch. +- To use a newly published epoch, open a **new** session. +- Compatible MCP Apps preview through the same bound session. + +## Playground owns its trace + +Only actions started in Playground join its ordered durable trace. Hook and MCP page operations +stay independent even while a Playground session is open — a trace records a deliberate sequence, +not everything that happened to be clicked. + +From a Playground trace you can replay or export the raw evidence, or promote selected durable +outcome and assertion evidence into a draft eval case. + +`script.run` is a production-mounted, trusted-local Playground operation: it runs only the +selected manifest-owned emitted script for the selected target, in a managed workspace, and +preserves bounded stdout and stderr, the exit code, cancellation, and raw event references. See +[Scripts and assets](../authoring/scripts-assets.mdx). + +Native prompts choose a server catalog selection — case, fixture, host, and pinned model — for +the selected epoch, rather than accepting a browser-supplied command or model. + +## The same session programmatically + +The public `startDevServer` export accepts the options the CLI flags map to (`root`, `port`, +`open`, `agentApi`, `installHosts`) and resolves to a `DevServerSession` exposing the loopback +`url`, a `status()` snapshot, and `close()`: + +```ts twoslash +import { startDevServer } from 'agent-bundle'; + +export const serve = async (): Promise => { + const session = await startDevServer({ port: 3100, root: process.cwd() }); + console.log(session.url); + await session.close(); +}; +``` + +## Development host installs + +Pass `--install-host ` more than once to install development variants into +the selected hosts: + +```sh +npx agent-bundle dev --install-host cursor --install-host claude +``` + +The first successful epoch uses the ordinary host installer, so Claude and Codex register the +plugin normally and read its files from their host-owned +`plugins/cache///` directory; Cursor reads +`~/.cursor/plugins/local/`. The installed root carries an `.agent-bundle-dev.json` with +schema version `1`, the project root, the host, and the installed epoch. + +Its MCP document always launches the framework CLI through the running dev server's Node +executable: + +```sh +agent-bundle dev proxy --root --server --target +``` + +Rebuilds never replace that stable command with an epoch path, and the contents of the host +process `PATH` do not affect whether the project-local framework can be spawned. + +Each later `artifact.available` event copies the new target into an immutable installed +generation. Top-level directories switch by atomic symlink (or Windows junction) rename and +top-level files by atomic sibling-file rename, so a host sees an old or a new complete entry and +no synchronized directory disappears between generations. A failed publication rolls the pointers +back to the prior generation and emits an `AB7202` diagnostic on `dev.host.sync`; a failed build +emits no `artifact.available` at all, so the last-good install is untouched. Re-sync writes the +host cache directly and does not invoke the Claude or Codex CLI again. + +Stopping the dev server leaves the marked development install in place. Hooks and Skills remain +on disk, while the stable proxy command fails closed until that project's dev server is running +again. + +## Live host MCP proxy + +A host can keep one stdio MCP process connected while `dev` rebuilds the generated server behind +it. The command, the `/mcp/host/` endpoint, and its discovery through the project's +development lock are documented in +[MCP servers and MCP Apps](../authoring/mcp.mdx). + +One rebuild rule matters here: a generated server that crashes is **not** silently respawned +within the same epoch. Calls remain failed until a successful rebuild swaps in a newly primed +epoch session. + +## Optional Agent API + +The Agent API is a separate, authenticated Streamable HTTP MCP endpoint for a Codex client. It is +**off by default** and is mounted only at `/mcp` on the existing loopback foreground server: + +```sh +AGENT_BUNDLE_AGENT_API_TOKEN='replace-with-a-secret' \ + npx agent-bundle dev --agent-api --no-open --port 3100 +``` + +`dev: { agentApi: true }` enables it from configuration, and `--no-agent-api` overrides that +setting. Startup fails **before serving** if the endpoint is enabled without +`AGENT_BUNDLE_AGENT_API_TOKEN`. The fixed token is read once, never logged, persisted, or +returned, and is required as standard `Authorization: Bearer` authentication. Clients may omit +`Origin`; a supplied origin must exactly match the foreground URL. When the endpoint is disabled +it is absent, not merely unauthorized. + +It has exactly thirteen fixed, ordered tools: + +| # | Tool | # | Tool | +| --- | --- | --- | --- | +| 1 | `project_status` | 8 | `hooks_list` | +| 2 | `skills_list` | 9 | `hook_simulate` | +| 3 | `skill_inspect` | 10 | `evals_list` | +| 4 | `artifacts_list` | 11 | `eval_run` | +| 5 | `artifact_inspect` | 12 | `eval_get` | +| 6 | `mcp_servers_list` | 13 | `diagnostics_list` | +| 7 | `mcp_invoke` | | | + +`eval_run` is limited to deterministic harnesses; it cannot select native hosts. Tool schemas +reject undeclared root, path, command, cwd, environment, harness, evidence, and outcome fields. +Artifact-backed calls may name an epoch id; otherwise they atomically lease the active epoch, so +a hot rebuild sends later calls to the new epoch while an admitted call stays pinned to its +original epoch. The transport is stateless, so an initialized client can issue later requests at +the same fixed URL once the foreground server returns. + +## Contributor UI HMR + +Working on the Workbench UI itself is a different loop from consuming a published one. Start it +only with a running foreground server: + +```sh +AGENT_BUNDLE_WORKBENCH_API_PROXY=http://127.0.0.1:3100 pnpm --filter agent-bundle-workbench dev +``` + +`packages/workbench/scripts/dev.mjs` requires that proxy URL. Published `agent-bundle dev` serves +prebuilt assets and project events; it does not run an Rsbuild development server. + +## Next + +- [Testing](./testing.mdx) — the proof levels behind the surfaces the Workbench exercises. +- [Evaluations](./evaluations.mdx) — eval runs, comparisons, and the Eval page. diff --git a/website/docs/en/guide/distribution/_meta.json b/website/docs/en/guide/distribution/_meta.json new file mode 100644 index 000000000..e2a750e4e --- /dev/null +++ b/website/docs/en/guide/distribution/_meta.json @@ -0,0 +1 @@ +["index", "validation", "installation", "preview-packages"] diff --git a/website/docs/en/guide/distribution/index.mdx b/website/docs/en/guide/distribution/index.mdx new file mode 100644 index 000000000..badde219f --- /dev/null +++ b/website/docs/en/guide/distribution/index.mdx @@ -0,0 +1,56 @@ +--- +description: 'How an agent-bundle project becomes something a host can install: build, validate, and ship each target directory as an independently distributable bundle.' +--- + +# Shipping a bundle + +`agent-bundle build` makes each target directory **independently distributable**. There is no +packaging step after the build and no per-host repackaging script: a target directory is the unit +you copy, publish, or hand to a host CLI. + +```sh +npx agent-bundle build --root . --output artifact +``` + +## The pipeline + +| Step | Command | What it produces | +| --- | --- | --- | +| Build | `agent-bundle build` | One directory per selected target, plus the `dist/` package build when the project declares `bin`/`lib`. | +| Validate | `agent-bundle validate --artifact --strict` | Content-addressed proof that the emitted bytes match the manifest, plus host-tool findings where a host publishes a validator. | +| Install | `agent-bundle install --from ` | The bundle registered in Claude, Codex, or Cursor. | + +The build already validates the project before it writes anything, so a separate `validate` run +against source is a fast pre-flight rather than a required stage. Validating the **artifact** is +the interesting one, because it needs no project sources at all. + +## What ships inside a target directory + +Every built target contains a generated `INSTALL.md` written with the bundle's **real** plugin +and marketplace names — not placeholders — so the file can be followed verbatim. The multi-host +`plugin` target also carries a generated `AGENTS.md` explaining how to install it into each host. + +Claude and Codex targets always include local marketplace manifests, which is what lets their +public CLIs install the emitted directory directly. Cursor, portable, and composite targets +include a standalone `install.mjs`, because Cursor exposes marketplace management but no +non-interactive plugin install verb. + +`agent-bundle.manifest.json` records every emitted file with its SHA-256, so validation compares +real bytes rather than checking that a path exists. The per-target layouts themselves are covered +in [Project structure](../start/project-structure.mdx). + +## The npm-facing half + +A project that also ships as an npm package has a second output: `dist/bin/.js` executables +and a library entry, described in [Package entries](../authoring/package-entries.mdx). When +package outputs and at least one host pack are built together, the framework also emits a +package-relative installer bin — see [Host installation](./installation.mdx). + +`agent-bundle prepack` is the gate for that half: it runs the release build, dry-runs `npm pack` +without scripts, and verifies packaged outputs, artifact hashes, bins, and versions. + +## In this section + +- [Artifact validation](./validation.mdx) — source and artifact validation, and what each host's own tooling contributes. +- [Host installation](./installation.mdx) — installing into Claude, Codex, and Cursor, and the install scopes each one accepts. +- [Preview packages](./preview-packages.mdx) — the pkg.pr.new release channel that stands in for npm today. diff --git a/website/docs/en/guide/distribution/installation.mdx b/website/docs/en/guide/distribution/installation.mdx new file mode 100644 index 000000000..8c8d8ad7b --- /dev/null +++ b/website/docs/en/guide/distribution/installation.mdx @@ -0,0 +1,111 @@ +--- +description: 'Install a built agent-bundle into Claude Code, Codex, or Cursor: the install command, host scopes, the standalone install.mjs, the package-relative installer bin, and Doctor.' +--- + +# Host installation + +Every built target directory contains a generated `INSTALL.md` with commands that use the +bundle's real plugin and marketplace names. The framework CLI performs those same operations: + +```sh +npx agent-bundle install claude --from artifact/claude --scope user +npx agent-bundle install codex --from artifact/codex +npx agent-bundle install cursor --from artifact/cursor +``` + +`--from` accepts either a target bundle directory or a **source-free artifact root**, as long as +the root contains the selected host's target directory. + +## What each host accepts + +| Host | Mechanism | Scopes | +| --- | --- | --- | +| Claude Code | Delegates to `claude plugin marketplace add` and `claude plugin install`. | `user`, `project`, `local` | +| Codex | Delegates to `codex plugin marketplace add` and `codex plugin add`. | `user` | +| Cursor | Copies the bundle into `~/.cursor/plugins/local/`, because Cursor publishes no non-interactive install verb. | `user` | + +Because Claude and Codex targets always ship local marketplace manifests, their public CLIs can +install the emitted directory directly. The installer fails with a typed diagnostic when the +selected host binary is unavailable, rather than reporting a success it did not achieve. Host +installation diagnostics are the `AB700x` family: bundle identity, host availability, scope, +command failure, and collision checks. + +## The standalone installer + +Cursor, portable, and composite targets include an `install.mjs` that copies the bundle into +`~/.cursor/plugins/local/` without overwriting collisions: + +```sh +npx agent-bundle install cursor --from artifact/cursor +# or, from the emitted target directory: +node ./install.mjs +``` + +Its staged copy is idempotent for identical content and refuses version or content collisions. It +never invokes `sudo` and never changes `PATH`. Artifact validation rejects a built-in target +whose required install surface is missing, so a bundle cannot ship without the installer it +promises. + +## The package-relative installer bin + +When package outputs ship one of those host packs, the build also emits a package-relative +installer bin. It uses the plugin name when no configured bin claims it, and +`-install` otherwise (with a numeric suffix if both are taken). Map that name to the +generated `dist/bin/*.js` file in `package.json`; consumers then run: + +```sh + install [--scope ] [--json] +``` + +Help lists only the hosts that were actually built. The executable locates the artifact directory +beside the installed package from `import.meta.url`, never the caller's working directory, so it +works from `node_modules` regardless of where it is invoked. **No npm lifecycle performs an +installation** — installing a package never mutates a host's plugin state. + +## Development installs are a different thing + +`agent-bundle dev --install-host ` maintains a *marked development* install that follows +successful rebuild epochs, with atomic generation switching and a stable proxy command. That is +covered in [Developer Workbench](../development/workbench.mdx) and is not the same operation as +`agent-bundle install`. + +## Reinstalling after a rebuild + +Every emitted installer — `agent-bundle install `, the package-relative bin, and the +standalone `install.mjs` — shares one replace policy. An identical copy is an `already-installed` +no-op. A copy of the **same version whose content hash differs** is replaced automatically, so +rebuilding without a version bump no longer needs an uninstall and `rm -rf`. A different version is +refused with `AB7005` unless you pass `--replace` (alias `--force`), and a foreign directory — one +this plugin's installer did not place — is refused either way. Cursor copies carry an install receipt +(`.agent-bundle-install.json`: plugin, version, host, content hash, owned files); replacement is in +place and touches owned files only, never unowned entries such as `state/`, and `--replace` adopts +a pre-receipt copy. Claude replacement runs `claude plugin uninstall --keep-data` before +reinstalling because `plugin update` is version-gated; Codex runs `codex plugin remove` before +`add`. The emitted `INSTALL.md` documents the same recipe per host. + +## Inspect an install without changing it + +```sh +npx agent-bundle doctor --host claude --host cursor --json +npx agent-bundle doctor --from artifact --json +``` + +Doctor is read-only. It probes hosts, inventories installed bundles, compares them against a +supplied bundle, checks registration proof, samples runtime endpoint health and identity, +inventories durable state, and re-runs the pinned process-free document and loader validators over +installed bytes. It never repairs anything. With `--from`, it reports the installed copy per host as +`current`, `stale` (`AB7308`), `version-mismatch` (`AB7309`), `foreign` (`AB7321`), or +`not-installed` (`AB7307`). + +| Code | Severity | Meaning | +| --- | --- | --- | +| `AB7316` | warning | An installed bundle's `state/` directory or one of its `*.sqlite`, `-wal`, or `-shm` files cannot be read with filesystem metadata operations. Doctor inventories state by directory entry and metadata only; it never opens a database. | +| `AB7317` | info | A live event runtime implements the older strict protocol and does not expose runtime identity. | +| `AB7318` | error | A live event runtime became unavailable, timed out, or returned an invalid status response during the bounded read-only identity probe. | +| `AB7319` | error | A host tree resolved from `doctor --from` violates its pinned document schemas or process-free loader rules; the message retains the originating build-validator code. | +| `AB7320` | error / info | Error when a `.cursor-plugin/plugin.json` install violates Cursor's pinned document schemas or token-location rules, or when a local plugin contains a symlink escaping `~/.cursor/plugins/local`; the inventory entry is reported as `corrupt`. Info when a `.claude-plugin/plugin.json` or root `plugin.json` install has no Cursor-side pinned static document contract, and the loader-recognized entry stays `installed`. | + +## Next + +- [Preview packages](./preview-packages.mdx) — where installable tarballs come from today. +- [Artifact validation](./validation.mdx) — the checks that gate a bundle before it is installed. diff --git a/website/docs/en/guide/distribution/preview-packages.mdx b/website/docs/en/guide/distribution/preview-packages.mdx new file mode 100644 index 000000000..feafc90b7 --- /dev/null +++ b/website/docs/en/guide/distribution/preview-packages.mdx @@ -0,0 +1,72 @@ +--- +description: 'The pkg.pr.new preview channel that stands in for npm: how previews are published, how to pin them, and what changes at the first npm release.' +--- + +# Preview packages + +Nothing is published to npm yet, deliberately. The current package names are placeholders, and +npm publishing is deferred until the final name is chosen. Until then, **pkg.pr.new is the +release channel**: every CI package-preview run publishes real, installable tarballs of all three +publishable workspace packages to a free continuous-release registry keyed by commit SHA and pull +request. + +The install commands and the runtime pairing rule are in +[Installation](../start/installation.mdx). This page is the channel itself: where previews come +from, how far you can trust them, and what pinning actually guarantees. + +## Where previews come from + +`.github/workflows/package-preview.yml` runs `pnpm preview:publish` after a full build, on every +pull request and on every push to `main`. It publishes `packages/agent-bundle`, +`packages/rsc-runtime`, and `packages/create-agent-bundle` with `--previewVersion --peerDeps +--no-compact --no-template`. + +Runs for `main` pushes use a **per-commit** concurrency group, so overlapping pushes cannot +cancel one another and every `main` commit has an installable snapshot. PR runs do cancel +superseded builds for the same PR, because only the latest preview of a PR matters. + +The "Publish pkg.pr.new preview" check on a PR or commit links to the exact URLs for that build. +Previews are built from the same `pnpm build` output the release gates verify — but they are not +npm releases, and they carry preview version strings. + +## Pinning, and one historical caveat + +The pairing rule and the install commands live in +[Installation](../start/installation.mdx); two details belong to the channel itself. + +A PR reference resolves to the **last preview published for that PR**, so `@1` means "PR #1's most +recent build" rather than a fixed commit. Short SHAs never move, which is why a lockfile should +carry one. + +Previews published before the peer rewrite landed (PR #46) still carry the original +`agent-bundle@^0.1.0` peer range, so pair-installing those older SHAs with npm still requires +`--legacy-peer-deps`. Anything newer installs with stock npm. + +The scaffolder ships on the same channel and is meant to be run rather than installed: + +```sh +npx https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@ my-plugin +``` + +A scaffolded project pins `agent-bundle` to the preview of the same commit the scaffolder came +from, so both sides of the pairing rule hold automatically. + +## What changes at the first npm release + +The first npm release will use +[npm package provenance](https://docs.npmjs.com/generating-provenance-statements): the publish +step exports `NPM_CONFIG_PROVENANCE=true` and runs the packed release gates before +`changeset publish`. `publishConfig.provenance` is already set. + +Before that path is enabled, the release owner has to resolve two things: the final package name +and license, and the repository-wide `"access": "restricted"` policy for `agent-bundle`, which is +not currently overridden with `publishConfig.access`. + +`pnpm release` runs the release gate — `pnpm pack:dry-run`, `pnpm audit:release`, and +`pnpm test:packed:release` — before publishing. That gate is release-only and does not replace the +ordinary `pnpm check` delivery gate. + +## Next + +- [Installation](../start/installation.mdx) — the exact install and pairing commands. +- [Artifact validation](./validation.mdx) — the `prepack` inventory gate these previews run through. diff --git a/website/docs/en/guide/distribution/validation.mdx b/website/docs/en/guide/distribution/validation.mdx new file mode 100644 index 000000000..b43f48699 --- /dev/null +++ b/website/docs/en/guide/distribution/validation.mdx @@ -0,0 +1,126 @@ +--- +description: 'Validate agent-bundle source and built artifacts, including strict Claude Code host validation, pinned Codex and Cursor schema checks, and the npm prepack gate.' +--- + +# Artifact validation + +Validation answers two different questions with one command. Against **source** it asks whether +the project describes a legal bundle; against an **artifact** it asks whether the emitted bytes +are the bytes the manifest promised. + +```sh +npx agent-bundle validate --root . # project source +npx agent-bundle validate --artifact artifact --strict # built bytes, no sources needed +``` + +`validate --artifact`, `mcp`, and `hooks` all work against a built artifact with the project +sources deleted. That is deliberate: an artifact that only validates while its source tree is +present is not really self-contained, and the `packed-deleted-source` proof level in +[Testing](../development/testing.mdx) exists for the same reason. + +## Content-addressed, not path-existence + +`agent-bundle.manifest.json` records a SHA-256 digest for every emitted file, including copied +scripts and assets. Artifact validation compares real bytes against those digests, so a +hand-edited generated file fails rather than passing because the path still exists. Referenced +files are checked too — a manifest-declared `logo` that is missing from the artifact or escapes +the deploy tree reports `AB6025`. + +Every diagnostic is one structured record: a stable `AB` code, a severity, a message, and usually +a `sourcePath` and a `recovery` hint. The diagnostic-gated commands — `build`, `prepack`, +`validate`, `doctor`, `install`, and `dev` — exit nonzero **only** when an error diagnostic is +present; warnings and infos never gate a build, a validation, or a dev rebuild. `eval` and +`inspect` also exit `1` for a failing or inconclusive trial or an invalid model, with no error +diagnostic involved — see the [CLI exit codes](../../reference/cli.mdx#exit-codes). + +## Claude Code strict validation + +When Claude Code is on `PATH`, artifact validation runs +`claude plugin validate --strict` for emitted `claude` and unified `plugin` targets: + +- Host **errors** become Agent Bundle errors. +- Host **warnings** stay warnings unless `agent-bundle validate --strict` is set. +- A missing binary is reported as an explicit informational **skip**, never as fabricated success. + +CI should use strict validation: + +```sh +npx agent-bundle validate --artifact artifact --strict +``` + +Use `--no-host-validation` when a deterministic schema-only check is required — a machine without +the hosts installed must still be able to run the same gate and get the same schema verdict. + +During development you can load a built target without installing it and verify registration: + +```sh +claude --plugin-dir artifact/claude plugin list --json +``` + +## Codex and Cursor: pinned schemas + +Neither Codex nor Cursor publishes a plugin-validation developer verb today, so Agent Bundle +validates their emitted JSON documents against its own vendored pinned schemas and treats host +schema generators as a **drift signal**, never as a substitute plugin contract. + +| Code | Severity | Meaning | +| --- | --- | --- | +| `AB6026` | info | Every Cursor report states that Cursor publishes no plugin-validate verb, and names the vendored schema pin used locally. | +| `AB6027` | error | A required generated Cursor document is missing, or a present plugin, marketplace, MCP, or hooks document is unreadable, invalid JSON, or rejected by its pinned schema. | +| `AB6028` | error | Generated bytes violate pinned Cursor loader evidence: manifest-candidate precedence selects a fallback manifest, a symlink resolves outside the bundle, or `CURSOR_PLUGIN_ROOT` appears outside loader-substituted fields. | +| `AB6029` | info / warning | The Cursor Agent version probe is unavailable (`ENOENT`, info) or could not complete (warning). Local pinned-schema validation still runs. | +| `AB6030` | info | The Codex CLI is unavailable, or the installed release publishes no plugin validation command. | +| `AB6031` | info / warning (error under `--strict`) | The Codex app-server schema-generation verb is unavailable, or its live output is missing or differs from the pinned generated hook schemas. | +| `AB6032` | error | A required Codex bundle document is missing, unreadable, invalid JSON, or fails its vendored pinned schema. | +| `AB6033` | error | A bounded Codex version or schema-generation command could not start, failed, timed out, exceeded 1 MiB of output, or produced unreadable output. | + +`AB6031` is the interesting one: when Codex's generated schema disagrees with the pinned +revision, the pin is only updated once Codex publishes the matching contract. Following live host +output automatically would turn an upstream change into a silent contract change here. + +## Agent Skills spec lint + +`AB6034` is an **error** on emitted bytes: a `SKILL.md` with valid YAML frontmatter but no +Markdown instruction body after it. The pinned Agent Skills specification requires frontmatter +followed by Markdown content, and the check runs against what the artifact actually contains +rather than against the authored source. + +## Hash pins versus repository-owned tables + +Hash pins cover vendored external content whose ground truth lives outside the repository and can +drift: host document schemas under `src/adapters/schemas/*` (with upstream URL, commit, and +SHA-256 recorded in `PROVENANCE.json`), the Agent Skills specification-derived schema pin in the +manifest `agentSkills` block, and emitted artifact files and source inputs for integrity. + +Repository-owned capability tables and evidence are **not** hashed. Capability evidence records +the observed host version (`observedVersion`) and target, while adapters carry a monotonic +`adapterRevision`. Git already versions repository-owned content; hashing it again inside the +repository is self-referential and causes churn on every table edit. + +## The npm prepack gate + +```sh +npx agent-bundle prepack --root . --output artifact --json +``` + +`prepack` runs the release build and `npm pack --dry-run --json --ignore-scripts`, then gates the +exact package and artifact inventory, manifest hashes, package bin targets, and release-version +agreement. `--output` is an artifact path relative to `--root` that overrides the configured +`output.distPath`, defaulting to `artifact`. Use it as an npm `prepack` script; `--ignore-scripts` +prevents recursion, and no npm lifecycle ever performs a host install. + +| Code | Meaning | +| --- | --- | +| `AB7010` | The dry-run npm inventory omits a package output, artifact manifest or file, install surface, or README. Include `dist` and the artifact directory in the package `files` allowlist. | +| `AB7011` | An on-disk artifact file no longer matches its manifest SHA-256. Rebuild, and do not modify generated host packs. | +| `AB7012` | A `package.json` bin points outside the packed `dist` output (including `src/`) or names a file npm omitted. Point it at the generated `dist/bin` file. | +| `AB7013` | `package.json`, normalized plugin metadata, a host manifest, or artifact provenance reports a different release version. Make every release identity agree. | + +A release build also refuses a project with **no** release version at all (`AB4013`), so a +published artifact never carries the `0.0.0-dev.` development fallback. A +declared `plugin.version` that disagrees with `package.json` reports the `AB4008` warning. + +## Next + +- [Host installation](./installation.mdx) — registering a validated bundle with a host. +- [Preview packages](./preview-packages.mdx) — how these outputs reach consumers today. diff --git a/website/docs/en/guide/start/_meta.json b/website/docs/en/guide/start/_meta.json new file mode 100644 index 000000000..7d40fbbbd --- /dev/null +++ b/website/docs/en/guide/start/_meta.json @@ -0,0 +1 @@ +["index", "installation", "quick-start", "project-structure"] diff --git a/website/docs/en/guide/start/index.mdx b/website/docs/en/guide/start/index.mdx new file mode 100644 index 000000000..277279ea3 --- /dev/null +++ b/website/docs/en/guide/start/index.mdx @@ -0,0 +1,93 @@ +--- +description: 'agent-bundle compiles one typed config into installable Claude Code, Codex, Cursor, and portable agent plugin artifacts.' +--- + +# Introduction + +agent-bundle compiles an agent plugin — Skills, hooks, MCP servers, and scripts, described +by one typed config — into installable artifacts for Claude Code, Codex, and Cursor, plus a +portable layout. You write the plugin once; the compiler emits each host's manifests and +wrappers. + +Node.js 22.19 or later is required. + +## The problem it solves + +Every agent host wants the same plugin expressed in its own layout: its own manifest +filenames, its own placeholder spellings for the plugin install root, its own hook document +shape, its own MCP server declaration. Writing that by hand means maintaining the same plugin +several times and discovering the disagreements after installation. + +agent-bundle inverts that. Host-specific layout is the compiler's job, so it stays out of your +source tree: + +```sh +npx agent-bundle build --root . +``` + +With `targets: ['plugin']`, that single command emits one multi-host bundle at `artifact/plugin/`: +`.claude-plugin/`, `.codex-plugin/`, and `.cursor-plugin/` manifests over shared `skills/`, +`hooks/`, `mcp/`, and `scripts/` directories. The bundle's generated `AGENTS.md` explains how to +install it into each host. Per-host layouts are available as the `claude`, `codex`, `cursor`, +and `portable` targets. + +## What the config owns + +One `agent-bundle.config.ts` at the project root describes the whole plugin: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { name: 'my-plugin', description: 'What it does.' }, // [!code highlight] + targets: ['plugin'], + skills: ['src/skills/*'], + hooks: { sessionStart: { handler: './src/session-start.ts' } }, + mcp: { servers: { tools: { entry: './src/mcp.ts' } } }, +}); +``` + +The same config also owns the npm package build — no second bundler config, no bin shims, no +hand-rolled stdio lifecycles. `bin` and `lib` entries (or the conventions `src/cli.ts`, +`src/index.ts`, and `src/mcp/.ts`) emit executable `dist/bin/.js` bundles and a +library output alongside the host artifacts. An MCP entry that default-exports a server factory +runs under a framework-owned stdio lifecycle. `tools.rsbuild` / `tools.rspack` is the one +bundler escape hatch. + +## The authoring model + +agent-bundle has one newcomer model, and it fits on four lines: + +1. **Authored source lives under `src/`.** Skills, commands, rules, scripts, MCP routes, state, + and providers all have conventional `src/` roots. A path is an identity: a module at + `src/mcp/curator/tools/status.tsx` *is* the `status` tool of the `curator` server. +2. **One small flat config.** `agent-bundle.config.ts` holds project identity, targets, and the + policy that no route file can own. +3. **JSX means rendering.** An executable route is one async default Server Component that does + the work and returns `Agent.*` nodes. There is no public `execute`/`render` split. +4. **Opt in to context.** Call `await agent()` inside that component only when you need host, + session, actor, workspace, capability, or state context. + +Everything above that line is power-tier reference: custom and remote MCP server modes, +prebuilt payloads, request-context providers, and the bundler escape hatch. + +## Evidence, not vibes + +A plugin that builds is not a plugin that works. agent-bundle ships separate proof levels — +route-unit, in-memory MCP, CLI dispatch, packed stdio, packed with source deleted, and +host-install — and each helper stamps the level it carried into its provenance. A pass at one +level is never reported as a receipt for another, and an assertion that needs stronger evidence +than the harness produced is `inconclusive` rather than silently passing. + +## Where to go next + +- [Installation](./installation.mdx) — install the preview tarballs that CI publishes today. +- [Quick start](./quick-start.mdx) — scaffold a project, or write the config by hand. +- [Project structure](./project-structure.mdx) — the conventional `src/` roots and the output layout. +- [Authoring](../authoring/index.mdx) — the configuration model and every authorable surface. + +The repository documents the same contracts in more depth: +[Framework mode](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/framework-mode.md) +is the whole authoring model on one screen, and +[Entry conventions](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/entry-conventions.md) +is the full package-build contract. diff --git a/website/docs/en/guide/start/installation.mdx b/website/docs/en/guide/start/installation.mdx new file mode 100644 index 000000000..eed016a0a --- /dev/null +++ b/website/docs/en/guide/start/installation.mdx @@ -0,0 +1,88 @@ +--- +description: 'Install agent-bundle from the pkg.pr.new preview channel, and what changes once the first npm release exists.' +--- + +# Installation + +## Requirements + +- **Node.js 22.19 or later** to run the compiler and the developer Workbench. +- **A package manager** — npm, pnpm, yarn, and bun all work; the examples below use npm. + +Generated executables target Node.js 22.12 or newer by default. `runtime: { node: '24.0' }` +raises that floor (it can never be lowered), and the selected floor is recorded as `runtime.node` +in the artifact manifest. + +## Install from the preview channel + +Nothing is published to npm yet. This is deliberate: the current package names are +placeholders, and the `agent-bundle` name on npm currently belongs to an unrelated project. +Until the first release, install the preview tarballs CI publishes for every commit and pull +request: + +```sh +npm i -D https://pkg.pr.new/ScriptedAlchemy/agent-bundle/agent-bundle@ +``` + +Use a PR number or the SHA of a commit whose package-preview run succeeded — every green `main` +commit has one. Short SHAs work, and a SHA is the right form for lockfiles and reproducible +setups; a PR number tracks that pull request's most recent build. + +### Pair the runtime package from the same commit + +Projects with route modules also need `@agent-bundle/runtime`, published to the same channel: + +```sh +npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/agent-bundle@5685521 +npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@5685521 +``` + +Install both from **one** SHA or PR number. `agent-bundle` declares `@agent-bundle/runtime` as +an optional peer (the runtime package declares no `agent-bundle` peer), and the publish +(`--peerDeps`) rewrites that peer range inside the `agent-bundle` preview tarball to the exact +preview version of the runtime built from the same commit, so a matched pair installs with +stock npm. Mixing two different SHAs fails with `ERESOLVE` by design — the range to inspect is +the one in the installed `agent-bundle` package. + +pnpm and yarn accept the same URLs (`pnpm add `, `yarn add agent-bundle@`). + +Preview versions are spelled `0.0.0-preview-`. They are built from the same output the +release gates verify, but they are not npm releases. See +[Preview packages](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/preview-packages.md) +for pinning details and where previews come from. + +## Optional dependencies + +These are installed only when a project uses the surface that needs them: + +| Package | Needed for | +| --- | --- | +| `@agent-bundle/runtime` | Route modules, rendered Skills, and rendered CLI commands or scripts. Generated entries import it the same way your code does. | +| `@rstest/core` | Running the framework test harness. It is an optional peer dependency, so a project that never tests routes never installs it. | +| `react` | Rendering route modules and asserting on them. Also an optional peer dependency. | +| `typescript` | `lib.dts` declaration generation, which resolves `typescript` from the project. | + +## Once npm releases exist + +The commands below are the **future** installation path. They do not work yet, because no npm +release has been cut: + +```sh +npm install --save-dev agent-bundle +npm create agent-bundle@latest my-plugin +``` + +The first npm release will use npm package provenance. Until the release owner picks the final +package name and license, pkg.pr.new previews are the release channel. + +## Verify the install + +```sh +npx agent-bundle --help +npx agent-bundle validate --root . +``` + +`validate` checks project source. Once you have built something, `validate --artifact ` +checks the built artifact instead, with no project sources required. + +Next: [Quick start](./quick-start.mdx). diff --git a/website/docs/en/guide/start/project-structure.mdx b/website/docs/en/guide/start/project-structure.mdx new file mode 100644 index 000000000..43bcefe58 --- /dev/null +++ b/website/docs/en/guide/start/project-structure.mdx @@ -0,0 +1,170 @@ +--- +description: 'The conventional src/ roots agent-bundle discovers, how config and conventions interact, and where build output lands.' +--- + +# Project structure + +An agent-bundle project is an ordinary Node package with one extra file at the root and a +conventional `src/` tree. Nothing here is mandatory: conventions fill the config in when it is +silent, and config always wins when both describe the same thing. + +## The layout + +```text +my-plugin/ +├── agent-bundle.config.ts # project identity, targets, and policy +├── package.json # authoritative release version and package identity +├── assets/ # static files copied byte-for-byte into every artifact +└── src/ + ├── skills//SKILL.md # one Skill per directory, with its own resources + ├── commands/*.md # host slash-command documents + ├── rules/*.mdc # host rule documents + ├── hooks/*.ts # lifecycle hook handlers referenced from config + ├── mcp/.ts # a handwritten stdio MCP server entry + ├── mcp// # or a generated server, one module per route + │ ├── tools/*.tsx + │ ├── resources/*.tsx + │ ├── prompts/*.tsx + │ ├── apps/*.tsx # browser MCP Apps compiled to self-contained HTML + │ └── layout.tsx # optional per-server layout around this server's routes + ├── scripts/.ts # artifact scripts (.tsx renders through the Agent renderer) + ├── cli.ts # a single package bin + ├── cli/**/*.ts # or a routed CLI, where nesting is the command path + ├── index.ts # the library entry + ├── layout.tsx # optional shared layout around every rendered route + ├── state.ts # project state definition + └── providers/.ts # request-context providers +``` + +## What each root means + +| Path | Surface | Opt out | +| --- | --- | --- | +| `src/skills//SKILL.md` | A Skill. Everything else in the directory ships as its resources. Ships with no declaration at all. | Remove the directory, or narrow the `skills` config globs. | +| `src/commands/*.md` | Flat host command documents. | Remove the file. | +| `src/rules/*.mdc` | Flat host rule documents. | Remove the file. | +| `src/mcp/.ts` | Stdio entry for a declared MCP server that names no `entry`, `command`, or `url`. | Declare `entry` explicitly. | +| `src/mcp//{tools,resources,prompts}/*` | Generated MCP server routes. The path supplies identity; each module supplies static `config`, schemas, and one async default Server Component. | Set `routes.servers.` to `custom`, `command`, or `remote`. | +| `src/mcp//apps/*` | Browser MCP App entries compiled to self-contained HTML and registered on the generated server. Static `config.resourceUri` is required. | Use a custom server, or prefix the file with `_`. | +| `src/scripts/.ts` | A plain script compiled to `scripts/.mjs` in every selected target. Nested modules are a hard error (`AB4808`). | Prefix a path segment with `_`, or claim the file with an explicit `scripts` entry. | +| `src/scripts/.tsx` | A rendered script: the async default component receives `argv` and `signal` and renders through the Agent renderer with the CLI output contract. | Rename to `.ts`, prefix a path segment with `_`, or claim the file. | +| `src/cli.ts` | A package bin named after `plugin.name`. | `bin: false` | +| `src/cli/**/*.{ts,tsx}` | Routed CLI commands compiled into one collision-checked command graph and one executable. Nesting is identity: `src/cli/library/audit.ts` runs as ` library audit`. Supersedes the `src/cli.ts` convention. | `bin: false`, `routes.cli: 'conventional'`, or prefix a path segment with `_`. | +| `src/index.ts` | The library output, with declarations. | `lib: false` | +| `src/layout.{ts,tsx}` | Shared document layout: default-exports one component receiving `{ children, route, signal }` that renders `Agent.Result` around every rendered route — generated MCP tools, resources, and prompts, rendered routed-CLI commands, projected MCP commands, and rendered scripts. Event routes and browser Apps are never wrapped. | Rename to `_layout.tsx`. | +| `src/mcp//layout.{ts,tsx}` | Per-server layout nested inside the root layout for that generated server's routes. | Rename to `_layout.tsx`, or set `routes.servers.` to a non-generated mode. | +| `src/state.ts` | Project state: default-exports `defineState`. Generated MCP, routed-CLI, and rendered-script request scopes mount it. | `state: false`, or rename to `_state.ts`. | +| `src/providers/.{ts,tsx}` | A request-context provider mounted at `providers.` on the request handle. | Prefix the file with `_`. | +| `assets/` | Static resources copied byte-for-byte into every target artifact's `assets/` directory. | Declare a top-level `assets` list instead. | + +Route and package entry conventions match `.ts` and `.tsx` files exactly; the state convention +is specifically `src/state.ts`. Discovered entries carry `provenance.kind: 'conventional'` in the +normalized model, so `agent-bundle inspect` tells you whether a file was picked up by convention +or claimed by config. + +## Config versus conventions + +The config holds what no single file can own — project identity, target selection, and policy: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { description: 'Evidence-backed project tools.', name: 'my-plugin' }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +Add an explicit declaration only when you need something the convention cannot express — a +different path, a target restriction, or an opt-out: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { description: 'Evidence-backed project tools.', name: 'my-plugin' }, + scripts: { + // Restricted to one target, so it cannot ride the convention. + 'detect-risk': { entry: './src/scripts/detect-risk.ts', targets: ['portable'] }, + }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +Source validation reports **informational** nudges — never errors — when a project shows a +pre-convention pattern: `AB4730` for a self-connecting stdio entry that a default-exported +factory would upgrade to the framework lifecycle shell, and `AB4731` / `AB4732` / `AB4733` when +`src/cli.ts`, `src/index.ts`, or `src/mcp/.ts` exists but explicit configuration +shadows it. The `bin: false` and `lib: false` opt-outs stay silent. + +## Where output lands + +`agent-bundle build` writes two independent things. + +### Host artifacts + +One directory per selected target under the artifact root. The CLI defaults that root to +`artifact/`, so it never collides with the package build below; `output.distPath` or `--output` +moves it: + +```text +artifact/ +├── agent-bundle.manifest.json # every emitted file with its SHA-256 +└── plugin/ # targets: ['plugin'] — one multi-host bundle + ├── .claude-plugin/ + ├── .codex-plugin/ + ├── .cursor-plugin/ + ├── bin/.mjs # the routed CLI, when src/cli/** exists + ├── skills/ + ├── hooks/ + ├── mcp/ + ├── scripts/ + ├── assets/ + ├── AGENTS.md + └── INSTALL.md +``` + +Per-host layouts are available as the `claude`, `codex`, `cursor`, and `portable` targets. +`agent-bundle.manifest.json` sits at the artifact root beside the target directories and records +every emitted file with its SHA-256, so artifact validation is content-addressed rather than a guess. + +`output.distPath` moves the artifact root; it never changes the framework-owned layout inside +each target. Precedence is the CLI `--output`, then `output.distPath`, then the default — +`artifact` for `agent-bundle build`, which also emits the package build, and `dist` for the +programmatic `build()` without `packageOutputs`. Values must be non-empty, project-root-contained +relative POSIX paths. + +### The npm package build + +When the project declares `bin`/`lib` — or provides them by convention — the same build also +writes the node-consumable package build under `dist/`: + +```text +dist/ +├── bin/.js # self-executing ESM, shebang, executable bit +├── .js # the library entry +└── **/*.d.ts # declarations, when lib.dts is on +``` + +`dist` is a mandatory-ignored directory: package outputs never enter project source snapshots or +Skill and asset discovery. The two outputs must not overlap: pointing `output.distPath` or +`--output` at `dist` on a project with package entries is `AB4706`. The default already keeps +them apart, and spelling it out is harmless: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + output: { distPath: 'artifact' }, + plugin: { description: 'A CLI plus a plugin.', name: 'my-plugin' }, + targets: ['portable', 'claude'], +}); +``` + +## Next steps + +- [Configuration model](../authoring/index.mdx) — the full config surface. +- [Skills](../authoring/skills.mdx), [Hooks](../authoring/hooks.mdx), + [MCP servers and Apps](../authoring/mcp.mdx) — one page per surface. +- [Scripts and assets](../authoring/scripts-assets.mdx) and + [Package entries](../authoring/package-entries.mdx) — the rest of the build output. diff --git a/website/docs/en/guide/start/quick-start.mdx b/website/docs/en/guide/start/quick-start.mdx new file mode 100644 index 000000000..e79789f07 --- /dev/null +++ b/website/docs/en/guide/start/quick-start.mdx @@ -0,0 +1,129 @@ +--- +description: 'Scaffold an agent-bundle project or write agent-bundle.config.ts by hand, then build and run the developer Workbench.' +--- + +# Quick start + +There are two ways in. The scaffolder emits a project that already passes its own `check`; the +manual path is four lines of config in an existing repository. + +## Scaffold a project + +The fastest start is `create-agent-bundle`. It prompts for a name, a template, and the host +targets: + +```sh +npx https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@ my-plugin +``` + +Once npm releases exist, this becomes `npm create agent-bundle`. Until then, use a commit SHA or +PR number from the [preview channel](./installation.mdx). + +A run that names both a directory and a template is treated as scripted and asks nothing — the +remaining values fall back to their defaults: + +```sh +npx https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@ my-plugin \ + --template mcp-server \ + --targets portable,codex,claude +``` + +### Templates + +| Template | What you get | +| --- | --- | +| `minimal` | A Skills-only plugin: one `src/skills//SKILL.md` directory and nothing else. | +| `mcp-server` | A stdio MCP server from one `src/mcp//tools/.tsx` route module plus one artifact script, with the framework test harness wired up. | +| `cli-tool` | An installable CLI through the `src/cli.ts` bin convention, plus a `src/index.ts` library export with declarations. | + +Every template ships a `check` script (validate, build, typecheck, tests) and validates with +zero diagnostics — including the `AB473x` migration nudges, because the templates are written +against the entry conventions from the start. The `mcp-server` template also starts with the +consumer test harness, each pool labeled with the proof level it carries. + +Scaffolded projects pin `agent-bundle` to an exact preview tarball from the same commit the +scaffolder came from, so the scaffolder and the framework it pins never disagree. See the +[create-agent-bundle README](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/packages/create-agent-bundle/README.md) +for every flag. + +## Or write the config by hand + +Describe the plugin in `agent-bundle.config.ts` at the project root: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { name: 'my-plugin', description: 'What it does.' }, + targets: ['plugin'], + skills: ['src/skills/*'], + hooks: { sessionStart: { handler: './src/session-start.ts' } }, + mcp: { servers: { tools: { entry: './src/mcp.ts' } } }, +}); +``` + +Most projects need even less than that, because the `src/` conventions fill the config in when +it is silent: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { description: 'Evidence-backed project tools.', name: 'my-plugin' }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +The release version comes from `package.json`. A `plugin.version` field still works as a +deprecated compatibility axis, but a value that disagrees with `package.json` reports the +`AB4008` warning. + +## Build, or work interactively + +```sh +npx agent-bundle build --root . # write installable artifacts to artifact/ +npx agent-bundle dev --root . # local workbench with live rebuilds +``` + +`build` validates the project and writes the artifact, plus the `bin`/`lib` package build when +declared. `dev` serves the loopback developer Workbench and rebuilds as inputs change: project +overview and diagnostics, Skill documents, the artifact tree with provenance and epoch +comparison, an artifact-bound MCP playground with the raw protocol trace, a hook playground that +runs the emitted wrapper, and eval runs. + +## Inspect what the compiler decided + +```sh +npx agent-bundle inspect --root . # normalized config and per-target plans +npx agent-bundle inspect --root . --skills # add the skill focus +npx agent-bundle validate --root . # check project source +``` + +`inspect` reads source configuration and shows the normalized model — which is where you confirm +that a convention was actually picked up. + +## Install the result + +Every built target directory contains a generated `INSTALL.md` with commands that use the +bundle's real plugin and marketplace names. With the `portable`, `codex`, and `claude` targets +built above, the host installs are: + +```sh +npx agent-bundle install claude --from artifact/claude --scope user +npx agent-bundle install codex --from artifact/codex +``` + +Add `cursor` to `targets` to build `artifact/cursor`, and `npx agent-bundle install cursor --from +artifact/cursor` installs it the same way. A `plugin` build has one `INSTALL.md` covering every host. + +For an install-free development loop against Claude Code: + +```sh +claude --plugin-dir artifact/claude plugin list --json +``` + +## Next steps + +- [Project structure](./project-structure.mdx) — what each `src/` root means and where output lands. +- [Configuration model](../authoring/index.mdx) — every config field and what owns it. +- [Skills](../authoring/skills.mdx) — the first surface most plugins author. diff --git a/website/docs/en/index.mdx b/website/docs/en/index.mdx new file mode 100644 index 000000000..cb9f085fb --- /dev/null +++ b/website/docs/en/index.mdx @@ -0,0 +1,240 @@ +--- +pageType: home +description: 'Compile skills, hooks, MCP servers, and scripts from one typed config into installable Claude Code, Codex, and Cursor artifacts.' +titleSuffix: ' - Agent plugin compiler for Claude Code, Codex, and Cursor' + +hero: + name: agent-bundle + text: One typed config, every agent host + tagline: Describe skills, hooks, MCP servers, and scripts once. Compile installable artifacts for Claude Code, Codex, and Cursor. + image: + src: /logo.svg + alt: agent-bundle logo + actions: + - theme: brand + text: Introduction + link: /guide/start/ + - theme: alt + text: Quick start + link: /guide/start/quick-start + +features: + - icon: 🧩 + title: One typed configuration + details: A single agent-bundle.config.ts holds identity, targets, and policy. The src/ conventions fill in everything it leaves silent, and config always wins when both describe the same thing. + link: /guide/authoring/ + span: 4 + - icon: 📚 + title: Skills + details: One directory per Skill with its SKILL.md and resources, discovered without a declaration and validated against the pinned Agent Skills specification. + link: /guide/authoring/skills + span: 4 + - icon: 🪝 + title: Lifecycle hooks + details: Seven events from sessionStart to workspaceOpen, written once in TypeScript and compiled into the wrapper each host actually spawns. + link: /guide/authoring/hooks + span: 4 + - icon: 🔌 + title: MCP servers and MCP Apps + details: Hand-written stdio servers, or generated servers where each tool, resource, and prompt is one module. Browser MCP Apps compile to self-contained HTML. + link: /guide/authoring/mcp + span: 4 + - icon: 📜 + title: Scripts, assets, and package entries + details: Plain or rendered scripts, byte-for-byte assets, and a CLI bin or library entry emitted from the same project — one build, both outputs. + link: /guide/authoring/scripts-assets + span: 4 + - icon: 🖥️ + title: Local Workbench + details: agent-bundle dev serves a loopback Workbench with diagnostics, the artifact tree, an MCP playground with the raw protocol trace, and a hook playground. + link: /guide/development/workbench + span: 4 + - icon: 🔬 + title: Evidence-driven testing + details: Route, protocol, CLI, packed, and host-install proof levels turn "it builds" into recorded evidence, and a pass at one level is never reported as a receipt for another. + link: /guide/development/testing + span: 4 + - icon: 📊 + title: Evaluations + details: Eval suites with pass, fail, and inconclusive semantics, run against the real emitted artifact rather than a mock of it. + link: /guide/development/evaluations + span: 4 + - icon: 📦 + title: Every target ships on its own + details: A built target directory is the unit you install — it carries its host manifests and a generated INSTALL.md. The artifact root beside it holds agent-bundle.manifest.json, the SHA-256 record that validation, MCP, hooks, and evals read. + link: /guide/distribution/ + span: 4 +--- + +import { Steps, Tab, Tabs } from '@rspress/core/theme'; + +## What you write, and what you get + +The input is one config file and a conventional `src/` tree. The output is one artifact root +holding a ready-to-install directory per host — each with its own host manifests, generated +wrappers, and installation instructions written with the bundle's real names — plus the root +`agent-bundle.manifest.json` that the whole artifact is validated against. + +
+
+ +### You write + +```ts title="agent-bundle.config.ts" +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { + name: 'release-tools', + description: 'Release-readiness checks.', + }, + hooks: { + sessionStart: { + handler: './src/hooks/session-start.ts', + }, + }, + targets: ['claude', 'codex', 'portable'], +}); +``` + +```text title="src/" +src/ +├── skills/release-review/ +│ ├── SKILL.md +│ └── references/policy.md +├── hooks/session-start.ts +├── mcp/status.ts +└── scripts/check-service.ts +``` + +Skills, the MCP server, and the script are picked up by convention. Only the hook needs a +declaration, because a handler has to be bound to an event. + +
+
+ +### The compiler emits + + + + +```text title="artifact/claude/" +artifact/claude/ +├── .claude-plugin/ +│ ├── plugin.json +│ └── marketplace.json +├── .mcp.json +├── hooks/ +│ ├── hooks.json +│ └── session-start-….mjs +├── mcp/mcp-status-….mjs +├── scripts/check-service.mjs +├── skills/release-review/ +│ ├── SKILL.md +│ └── references/policy.md +└── INSTALL.md +``` + + + + +```text title="artifact/codex/" +artifact/codex/ +├── .codex-plugin/plugin.json +├── .agents/plugins/marketplace.json +├── .mcp.json +├── hooks/ +│ ├── hooks.json +│ └── session-start-….mjs +├── mcp/mcp-status-….mjs +├── scripts/check-service.mjs +├── skills/release-review/ +│ ├── SKILL.md +│ └── references/policy.md +└── INSTALL.md +``` + + + + +```text title="artifact/portable/" +artifact/portable/ +├── plugin.json +├── mcp.json +├── mcp/mcp-status-….mjs +├── scripts/check-service.mjs +├── skills/release-review/ +│ ├── SKILL.md +│ └── references/policy.md +├── install.mjs +└── INSTALL.md +``` + +The Agent Plugins open standard has no hook surface. The hook above declares no `targets`, so it +inherits only the selected targets that support hooks and `portable` is skipped here without a +diagnostic. Naming it explicitly — `targets: ['portable']` — is `AB4204` at build time instead. + + + + +Generated wrapper names end in a short digest of the declaration they were compiled from, not of +their contents. `artifact/agent-bundle.manifest.json` records every emitted file with its SHA-256, so +later validation compares real bytes rather than checking that a path exists. + +
+
+ +## From source to installed plugin + + + +### Describe + +Write `agent-bundle.config.ts` and put Skills, hooks, MCP routes, and scripts under `src/`. +`agent-bundle inspect` shows the normalized model, so you can confirm which files were picked up +by convention and which were claimed by config. + +### Develop + +`agent-bundle dev` rebuilds on every change and serves the developer +[Workbench](/guide/development/workbench) on loopback: diagnostics, Skill documents, the artifact +tree with provenance, and playgrounds that drive the emitted MCP server and hook wrappers. + +### Prove + +Test against the artifact with [proof levels](/guide/development/testing) that stay distinct — +from route-unit tests up to a bundle installed through the real host CLI — and run +[evaluations](/guide/development/evaluations) whose results are pass, fail, or inconclusive. + +### Ship + +`agent-bundle build` validates the project and writes one directory per target. +[Validation](/guide/distribution/validation) checks the artifact against its manifest, and +[installation](/guide/distribution/installation) uses each host's own install path. + + + +## One source, every host + +| Target | What it emits | Installed with | +| --- | --- | --- | +| `claude` | The Claude Code plugin layout, with plugin and local marketplace manifests. | `claude plugin marketplace add` and `claude plugin install`, or `agent-bundle install claude`. | +| `codex` | The Codex plugin layout, with plugin and local marketplace manifests. | `codex plugin marketplace add` and `codex plugin add`, or `agent-bundle install codex`. | +| `cursor` | The Cursor plugin layout. | The generated `install.mjs`, or `agent-bundle install cursor`. | +| `portable` | The [Agent Plugins](https://agent-plugins.org) open standard — Skills and MCP servers — read natively by Cursor, Codex, VS Code, GitHub Copilot, Kiro, and ChatGPT. | The generated `install.mjs`. | +| `plugin` | One multi-host bundle carrying the Claude, Codex, and Cursor manifests over shared component directories. | `install.mjs` or any of the host CLIs. | + +Hosts differ in what they can load, so the compiler says so at build time: a surface you select +for a target that cannot express it is a reported diagnostic, never a quiet omission. The one +deliberate exception is a hook with no `targets` of its own, which inherits only the hosts that +support hooks — as the `portable` tab above shows — rather than failing. Every diagnostic has a +stable `AB` code documented in the [diagnostics reference](/reference/diagnostics). + +## Start here + +- [Installation](/guide/start/installation) — requirements and the `create-agent-bundle` + scaffolder. +- [Quick start](/guide/start/quick-start) — a project from config to installed plugin. +- [Examples](/examples/) — runnable products, from a Skills starter, hook and script traces, and + an interactive MCP App to a complete media-management plugin, plus two advanced references. +- [Type API](/api/) — the generated reference for every public `agent-bundle` export. diff --git a/website/docs/en/reference/_meta.json b/website/docs/en/reference/_meta.json new file mode 100644 index 000000000..714e233ef --- /dev/null +++ b/website/docs/en/reference/_meta.json @@ -0,0 +1,14 @@ +[ + "index", + "cli", + "configuration", + "targets-artifacts", + "hosts", + "events", + "notices", + "diagnostics", + "runtime-environment", + "security", + "limitations", + "api" +] diff --git a/website/docs/en/reference/api.mdx b/website/docs/en/reference/api.mdx new file mode 100644 index 000000000..e3a2eee96 --- /dev/null +++ b/website/docs/en/reference/api.mdx @@ -0,0 +1,49 @@ +--- +description: 'The generated agent-bundle type API: which entry points it covers, how it is produced, and why generated symbol documentation is language-neutral.' +--- + +# Type API + +The symbol-level reference is **generated from the source**, not written by hand. Browse it at +[Type API](/api/), or reach it from the top navigation. + +## What it covers + +Every public entry point is documented from its declarations: + +| Entry point | Contents | +| --- | --- | +| `agent-bundle` | The authoring and orchestration surface: `defineSkill`, `canonicalAgentEvents`, `startDevServer`, `runEvals`, `compareEvals`, the eval harness factories, and the artifact-manifest helpers. | +| `agent-bundle/api` | The programmatic compiler: `build`, `validate`, `inspect`, `prepack`, their option and result types, and the `AgentComponentKind` / `componentKindCapability` component-kind helpers. | +| `agent-bundle/config` | `defineConfig` and the configuration types. | +| `agent-bundle/test` | The route-testing harness, matchers, and contract matrices. | +| `agent-bundle/test/browser` | The MCP App bridge harness for browser-rendered views. | +| `agent-bundle/eval` | Eval suite definitions, assertions, and run comparison. | +| `agent-bundle/rstest` | The Rstest configuration helper. | +| `agent-bundle/meta` | The build-time project identity constant, replaced by the compiler in every compiled surface. | +| `agent-bundle/mcp-apps` | The MCP App resource registry contract, replaced by the compiler in local MCP servers. | +| `agent-bundle/cli-entry` | The routed-CLI shell every generated CLI executable is built on. | +| `agent-bundle/mcp-entry` | The stdio MCP entry shell every generated MCP server is wrapped in. | +| `agent-bundle/routes` | The route-module authoring types (`AgentEventRouteProps`, `ToolRouteProps`, `CliRouteProps`, the `config` shapes) and `appResourceUri`, the static reference to a sibling MCP App the compiler resolves to its `resourceUri`. | + +Because it is generated, it always matches the shipped types: signatures, unions, defaults, and +the error classes each surface can throw. When this Reference section and the generated pages +disagree, the generated pages are correct. + +## Generated pages are language-neutral + +The site mirrors the generated reference under both locales, so [/api/](/api/) and +[/zh/api/](/zh/api/) resolve for readers of either language. The **content** is not translated: +the symbol documentation is extracted from source comments and stays in the language it was +authored in. + +That is deliberate. A translated copy of generated output would drift from the source on the first +refactor, and a symbol reference whose types are current but whose prose is stale is worse than +one that is honestly monolingual. Hand-written pages — everything in the Guide and the rest of +this Reference — are complete translations. + +## Where to start + +- Compiling from a script: `build` and `validate` in `agent-bundle/api`. +- Writing tests: `renderRoute`, `expectDocument`, and the matrices in `agent-bundle/test`, explained in [Testing](../guide/development/testing.mdx). +- Writing evals: `defineEvalSuite` and the assertions in `agent-bundle/eval`, explained in [Evaluations](../guide/development/evaluations.mdx). diff --git a/website/docs/en/reference/cli.mdx b/website/docs/en/reference/cli.mdx new file mode 100644 index 000000000..086c2f08d --- /dev/null +++ b/website/docs/en/reference/cli.mdx @@ -0,0 +1,186 @@ +--- +description: 'The complete agent-bundle command-line surface: dev, build, prepack, install, doctor, validate, eval, inspect, mcp, and hooks, with every option and exit code.' +--- + +# CLI + +```sh +npx agent-bundle --help +npx agent-bundle --version +``` + +## Commands + +| Command | Purpose | +| --- | --- | +| `agent-bundle dev` | Serve the packaged development workbench on loopback. | +| `agent-bundle dev proxy` | Bridge host stdio MCP traffic to a running development server. | +| `agent-bundle build` | Build a validated Agent Bundle artifact. | +| `agent-bundle prepack` | Build and validate the npm pack inventory. | +| `agent-bundle install ` | Install a built bundle into a supported host. | +| `agent-bundle doctor` | Inspect host installs and runtime endpoints without changing them. | +| `agent-bundle validate` | Validate project source or one artifact. | +| `agent-bundle eval` | Run deterministic or native eval suites against a built artifact. | +| `agent-bundle eval compare` | Compare two persisted eval runs. | +| `agent-bundle inspect` | Inspect normalized targets and adapter plans. | +| `agent-bundle mcp list` / `invoke` / `run` | Operate an MCP server from an artifact. | +| `agent-bundle hooks list` / `simulate` | Inspect and simulate generated hooks. | + +## Shared option groups + +**Source commands** — `build`, `prepack`, `validate`, `eval`: + +| Option | Default | Meaning | +| --- | --- | --- | +| `--root ` | `process.cwd()` | Project root. | +| `--config ` | — | Configuration file relative to `--root`. | +| `--mode ` | `production` | Configuration mode. | +| `--target ` | all configured | Target to select. Repeatable. | +| `--json` | off | Write one machine-readable JSON document. | + +**Artifact commands** — `mcp list`, `mcp invoke`, `mcp run`, `hooks list`, `hooks simulate`: +the same `--root`, `--config`, `--mode`, and `--json`, plus `--artifact ` to use exactly +one built artifact and `--target ` to select the artifact target. `--target` is +**required** for `mcp list`, `mcp invoke`, `mcp run`, and `hooks simulate`. + +## dev + +| Option | Default | Meaning | +| --- | --- | --- | +| `--root ` | `process.cwd()` | Project root. | +| `--port ` | ephemeral | Loopback TCP port. | +| `--agent-api` / `--no-agent-api` | config `dev.agentApi` | Enable or disable the authenticated Agent API on `/mcp`. | +| `--install-host ` | none | Install and re-sync a development host. Repeatable; `claude`, `codex`, or `cursor`. | +| `--open` / `--no-open` | `--no-open` | Open the workbench after the foreground server starts. | + +`dev` runs in the foreground and closes the session on a termination signal. See +[Developer Workbench](../guide/development/workbench.mdx). + +### dev proxy + +| Option | Default | Meaning | +| --- | --- | --- | +| `--server ` | **required** | Generated MCP server name. | +| `--target ` | `portable` | Generated target containing the MCP server. | +| `--url ` | discovered | Explicit loopback development server origin. | + +The proxy inherits `--root` from the parent `dev` command and writes diagnostics to stderr. + +## build and prepack + +Both take the source option group plus: + +| Option | Default | Meaning | +| --- | --- | --- | +| `--output ` | config `output.distPath`, else `artifact` | Artifact output path relative to `--root`. `dist` belongs to the package build. | + +Both build package outputs when the project declares `bin` or `lib`. + +## install + +```sh +agent-bundle install [--from ] [--scope ] [--replace] [--json] +``` + +| Option | Default | Meaning | +| --- | --- | --- | +| `` | **required** | `claude`, `codex`, or `cursor`. | +| `--from ` | `process.cwd()` | Target bundle directory or artifact root. | +| `--scope ` | `user` | `user`, `project`, or `local`. Claude accepts all three; Codex and Cursor are user-scoped. | +| `--replace` (alias `--force`) | off | Replace an existing agent-bundle install of this plugin even when its version differs. Without it, an identical copy is an `already-installed` no-op, a same-version copy whose content hash differs is replaced automatically, and a different version is `AB7005`. Foreign directories are always refused (`AB7005`). | + +The emitted standalone `install.mjs` and the package-relative installer bin accept the same +`--replace`. Cursor copies carry an install receipt (`.agent-bundle-install.json`), replacement +touches owned files only, and `--replace` adopts a pre-receipt copy; Claude replacement runs +`claude plugin uninstall --keep-data` before reinstalling and Codex runs `codex plugin remove` +before `add`. + +## doctor + +| Option | Default | Meaning | +| --- | --- | --- | +| `--host ` | all supported | Host to inspect. Repeatable; `claude`, `codex`, or `cursor`. | +| `--from ` | — | Target bundle directory or artifact root to compare against. | +| `--json` | off | Write one machine-readable JSON document. | + +Doctor exits `1` when it reports any error diagnostic, and never modifies anything. With `--from`, +it reports the installed copy per host as `current`, `stale` (same version, different content — +`AB7308`), `version-mismatch` (`AB7309`), `foreign` (`AB7321`), or `not-installed` (`AB7307`). + +## validate + +| Option | Default | Meaning | +| --- | --- | --- | +| `--artifact ` | — | Validate exactly this built artifact instead of project source. | +| `--host-validation` / `--no-host-validation` | on | Run installed host developer tools for compatible built targets. | +| `--strict` | off | Promote host-tool warnings to errors. | + +## eval + +| Option | Default | Meaning | +| --- | --- | --- | +| `--artifact ` | built from source | Evaluate exactly this built artifact. | +| `--case ` | all | Eval case id to run. Repeatable. | +| `--suite ` | all | Eval suite name to run. Repeatable. | +| `--harness ` | `deterministic` | `deterministic`, `claude`, or `codex`. | +| `--trials ` | suite-declared | Run this many trials of every selected case. A positive integer, at most `100`. | + +`eval compare ` takes two persisted run ids and inherits the source options +from `eval`. + +## inspect + +`inspect` takes `--root`, `--config`, `--mode`, `--json`, and a single `--target ` filter +(not repeatable), plus **at most one** focus flag: + +| Flag | Focus | +| --- | --- | +| `--bundler` | The synthesized bundler configuration. | +| `--hooks` | Hooks. | +| `--routes` | The compiled route graph. | +| `--skills` | Skills. | +| `--state` | State lifetimes. | + +Passing more than one focus is an error. `inspect` exits `1` when the normalized model is invalid. + +Every inspection plan reports each component with its canonical kind — `agent`, `cli`, `command`, +`event-route`, `hook`, `lsp`, `mcp-app`, `mcp-server`, `native-diagnostics`, `native-extension`, +`rule`, `script`, `skill` (`AgentComponentKind` from `agent-bundle/api`) — as `selected` or +`skipped` with its cause, plus a `kinds` matrix: one entry per kind with the target's own four-state +capability judgment and selected/omitted counts. The human output prints one line per target, +each omission and its reason, then `kinds this host cannot emit:`. Filesystem `src/events` routes +report as `event-route`, judged by the host's `event:` row, not as `hook`. + +## mcp + +`mcp list` requires `--server `. `mcp invoke` requires `--server` and `--tool `, and +takes exactly one of `--input ` or `--input-file `; the parsed value must be a JSON +object, not an array or scalar. + +`mcp run` requires `--server` and runs one stdio server in the foreground with inherited stdio: + +| Option | Default | Meaning | +| --- | --- | --- | +| `--env-file ` | conventional set | Load exactly this `.env` file, replacing the project-root set. Repeatable. | +| `--no-env` | off | Launch without loading any `.env` files. Mutually exclusive with `--env-file`. | +| `--plugin-root ` | project root | Expand env plugin-root anchors against this root. | + +Because the server owns the JSON-RPC channel for the whole foreground run, `mcp run` writes +nothing of its own to stdout. See [Runtime environment](./runtime-environment.mdx) for the +`.env` precedence rules and what the plugin-root anchor changes. + +## hooks + +`hooks list` reports the hooks in an artifact. `hooks simulate` requires `--hook ` (an id or +a name) and the same `--input` / `--input-file` pair as `mcp invoke`. + +## Exit codes + +| Code | Meaning | +| --- | --- | +| `0` | Success, including `--help` and `--version`. | +| `1` | A reported failure: an error diagnostic, an invalid model from `inspect`, a failing or inconclusive eval run, or an uncaught error written to stderr as one `AB5000` diagnostic. | +| `2` | An argument-parsing failure from the command-line parser. | + +An eval run exits `1` when any trial **fails or is inconclusive** — an inconclusive trial produced +no evidence, so it cannot report success either. diff --git a/website/docs/en/reference/configuration.mdx b/website/docs/en/reference/configuration.mdx new file mode 100644 index 000000000..ed6517e13 --- /dev/null +++ b/website/docs/en/reference/configuration.mdx @@ -0,0 +1,167 @@ +--- +description: 'Field reference for agent-bundle.config.ts: every top-level key, its accepted shapes, defaults, and validation rules, including the deprecated plugin.version field.' +--- + +# Configuration + +The conceptual model — what config owns versus what the `src/` conventions own — is in +[Configuration model](../guide/authoring/index.mdx). This page is the field contract. + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { description: 'Evidence-backed project tools.', name: 'my-plugin' }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +## Top-level fields + +| Field | Type | Default | +| --- | --- | --- | +| `plugin` | `{ name, description?, logo?, ... }` | **Required.** | +| `targets` | `string[]` | Adapter-selected. | +| `skills` | `string[]` | The `src/skills/*` convention. | +| `hooks` | `Partial>` | The `src/hooks/*` convention. | +| `mcp` | `{ servers: Record }` | The `src/mcp/*` convention. | +| `scripts` | `Record` | The `src/scripts/*` convention. | +| `assets` | `string[]` | The root `assets/` convention. | +| `bin` | `false \| Record` | The `src/cli.ts` convention. | +| `lib` | `false \| string \| { entry, dts? }` | The `src/index.ts` convention. | +| `routes` | Route-graph policy | Convention-derived. | +| `output` | `{ distPath? }` | `artifact` from the CLI; `dist` from `build()` without `packageOutputs`. | +| `runtime` | `{ node }` | Node 22.12. | +| `payload` | `Record` | None. | +| `state` | `false` | The `src/state.ts` convention. | +| `marketplace` | `boolean` | Adapter-selected. | +| `dev` | `{ agentApi?, contracts?, runtime? }` | None. | +| `evals` | `{ include?, runsDir?, semanticGrader? }` | See below. | +| `tools` | `{ rsbuild?, rspack? }` | None. | + +Host-scoped extension keys (`claude`, `codex`, `cursor`, `portable`) are contributed by target +adapters through declaration merging on `AgentBundleConfigExtensions`. Extension values must be +strict finite JSON (`AB4500`), and host-specific values stay in their adapter rather than in +compiler core. The field-by-field list of every adapter-owned key is in +[Host-scoped declarations](../guide/authoring/index.mdx#host-scoped-declarations). + +### Generated type definitions + +The config is a TypeScript contract, not a runtime schema: `defineConfig` accepts an +`AgentBundleConfig` (or a factory returning one), and `validateSource` enforces the rules on this +page with structured diagnostics. The exact shapes below are generated from the package source +by TypeDoc on every documentation build, so they cannot drift from the published types. + +| Field | Type definition | +| --- | --- | +| whole config | [`AgentBundleConfig`](../api/types/index.AgentBundleConfig.md) · [`defineConfig`](../api/functions/index.defineConfig.md) · [`ConfigFactory`](../api/types/index.ConfigFactory.md) · [`ConfigFactoryContext`](../api/interfaces/index.ConfigFactoryContext.md) | +| `mcp` | [`AgentBundleMcpConfig`](../api/interfaces/index.AgentBundleMcpConfig.md) · [`AgentBundleMcpServer`](../api/interfaces/index.AgentBundleMcpServer.md) · [`AgentBundleMcpApp`](../api/interfaces/index.AgentBundleMcpApp.md) · [`McpTransport`](../api/types/index.McpTransport.md) | +| `payload` | [`AgentBundlePayloadConfig`](../api/types/index.AgentBundlePayloadConfig.md) · [`AgentBundlePayloadEntry`](../api/interfaces/index.AgentBundlePayloadEntry.md) · [`AgentBundlePrebuiltEntry`](../api/interfaces/index.AgentBundlePrebuiltEntry.md) | +| `output` | [`AgentBundleOutputConfig`](../api/interfaces/config.AgentBundleOutputConfig.md) | +| `runtime` | [`AgentBundleRuntimeConfig`](../api/interfaces/index.AgentBundleRuntimeConfig.md) | +| `dev` | [`AgentBundleDevConfig`](../api/interfaces/index.AgentBundleDevConfig.md) · [`AgentBundleDevContractsConfig`](../api/interfaces/index.AgentBundleDevContractsConfig.md) · [`AgentBundleDevRuntimeConfig`](../api/interfaces/api.AgentBundleDevRuntimeConfig.md) | +| host extensions | [`AgentBundleConfigExtensions`](../api/interfaces/index.AgentBundleConfigExtensions.md) · [`AgentBundlePortableConfig`](../api/interfaces/config.AgentBundlePortableConfig.md) | +| validation | [`validateSource`](../api/functions/config.validateSource.md) · [`validateModel`](../api/functions/config.validateModel.md) · [`loadConfig`](../api/functions/config.loadConfig.md) | + +## plugin + +| Field | Rule | +| --- | --- | +| `name` | Required. The host-native plugin slug, never the npm package name. | +| `description` | One sentence written into generated manifests. | +| `logo` | Project-relative image path. Missing, non-file, or outside the project reports `AB4012`; missing from the built artifact or escaping the deploy tree reports `AB6025`. | +| `version` | **Deprecated.** | + +`package.json` is authoritative for release identity. Declare the release version only there: +a `plugin.version` that disagrees with `package.json` reports the `AB4008` warning, and a release +build with no version anywhere is refused with `AB4013` rather than shipping the +`0.0.0-dev.` development fallback. The field remains only for compatibility and +will be removed under the normal breaking-change policy. + +## hooks + +Keys are the seven canonical events: `sessionStart`, `beforeTool`, `afterTool`, `stop`, +`agentStart`, `agentStop`, `workspaceOpen`. A value is one entry, or an array of entries, each +either a module path or `{ handler, tools?, targets?, timeout?, args? }`. How each canonical +event and tool selector lowers to a host-native event and matcher is the generated +[Event and hook matrix](./events.md). + +The **hook result** contract is documented in full in [Hooks](../guide/authoring/hooks.mdx). One +field is worth repeating because it is easy to get wrong: `reason` is a non-empty string that is +valid **only** when denying a `beforeTool`, `stop`, or `agentStop` hook, and denying one of those +without a reason fails. + +## output and runtime + +`output.distPath` is the artifact output directory relative to the project root. The CLI +(`build`, `prepack`, `dev`) defaults it to `artifact`, because the CLI also runs the package build +and that owns `dist/`; the programmatic `build()` defaults to `dist` unless `packageOutputs: true`. +The per-invocation `--output` flag wins, but is subject to the same project-root +containment check; an output path that is absolute or external, has an invalid shape, or lands +in a reserved compiler namespace is unsupported (`AB4707`–`AB4709`). + +`runtime.node` is a minimum Node.js version in `major.minor[.patch]` form. It can only raise the +default floor for generated executables, never lower it, and the selected floor is recorded as +`runtime.node` in the artifact manifest. The floor itself is described in +[Configuration model](../guide/authoring/index.mdx). + +## payload + +Keys are artifact-root destination directories — one safe path segment outside the +compiler-owned namespaces — and values are already-built source directories. Payload trees are +copied byte-for-byte and are **opaque to the compiler**, which cannot rewrite their internal +sibling references, so stable names are the correctness contract. Integrity stays +content-addressed through the artifact manifest. + +## evals + +Accepts exactly three keys; anything else is rejected. + +| Field | Default | Rule | +| --- | --- | --- | +| `include` | `['evals/**/*.eval.ts']` | A non-empty array of relative patterns that never escape the project root. | +| `runsDir` | `.agent-bundle/runs` | A relative child directory of the project root. | +| `semanticGrader` | none | Must contain exactly `harness` and `model`. `harness` must be `'claude'`; `model` must be a safe model identifier, not a path. | + +Provider credential material in the `evals` block is rejected outright +(`EVAL_CREDENTIAL_REJECTED`) — Agent Bundle reuses the host CLI's existing signed-in session. See +[Security](./security.mdx). + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + evals: { + include: ['evals/**/*.eval.ts'], + runsDir: '.agent-bundle/runs', + semanticGrader: { harness: 'claude', model: 'claude-sonnet-4-5' }, + }, + plugin: { description: 'Evidence-backed project tools.', name: 'my-plugin' }, + targets: ['portable', 'claude'], +}); +``` + +## dev + +Development-only settings that never become part of a built artifact. + +| Field | Meaning | +| --- | --- | +| `dev.agentApi` | Exposes the authenticated, loopback-only Agent API from `agent-bundle dev`. The `--agent-api` / `--no-agent-api` flags override it. | +| `dev.contracts.fixtures` | **Required when `dev.contracts` is set.** Project-relative module whose default export maps route ids to contract fixtures. Declaring `dev.contracts` switches `agent-bundle dev` from adopting every epoch directly to gating host-facing adoption on the development contract matrix: an epoch whose checks fail still publishes to the Workbench playground, but live host connections and development installs keep the last passing epoch (`AB7211`). A malformed block, a fixtures module that escapes the project root, cannot load, or exports the wrong shape is `AB7210`. | +| `dev.contracts.server` | The MCP server the matrix checks. Optional only when the project compiles exactly one server. | +| `dev.runtime.provider` | The development runtime provider module. | + +## tools + +The single bundler escape hatch. Both fragments merge last-but-bounded into every bundler config +the framework synthesizes, and the artifact invariant assertions still run after the merge, so a +hatch value that breaks an artifact contract is a hard diagnostic rather than a silent override +(`AB472x`). + +The hatch executes under two bundler engine copies: artifact scripts, MCP entries, hooks, and the +package build compile through Rslib's nested Rsbuild/Rspack, while MCP App views compile through +the workspace-pinned `@rsbuild/core`. Never construct plugins or perform `instanceof` checks +against an imported `@rspack/core` — use the `utils` argument passed to `tools.rspack` mutator +functions (`(config, { rspack }) => ...`), which always hands you the executing engine's own +`rspack` object. diff --git a/website/docs/en/reference/index.mdx b/website/docs/en/reference/index.mdx new file mode 100644 index 000000000..114b1a0ca --- /dev/null +++ b/website/docs/en/reference/index.mdx @@ -0,0 +1,52 @@ +--- +description: 'Reference material for agent-bundle: the CLI surface, configuration fields, target artifacts, runtime environment, security boundaries, limitations, and the generated type API.' +--- + +# Reference + +The Guide explains how to build and ship a bundle. This section is the lookup half: exact flags, +exact field names, exact defaults, and the boundaries the framework refuses to cross. + +Nothing here repeats a walkthrough. Where a concept is already explained in the Guide — the +configuration model, the seven hook events, the proof levels — these pages link to it and record +only the contract. + +| Page | Answers | +| --- | --- | +| [CLI](./cli.mdx) | Every command, argument, option, default, and exit code. | +| [Configuration](./configuration.mdx) | Every `agent-bundle.config.ts` field, its type, and its validation rule. | +| [Targets and artifacts](./targets-artifacts.mdx) | What each target emits, and the artifact manifest contract. | +| [Host capability matrix](./hosts.md) | The pinned per-host capability tables: versions, manifests, install surfaces, path tokens, MCP transports, plugin components. Generated at build time. | +| [Event and hook matrix](./events.md) | Canonical events to native events per host, tool selectors to native matchers, deferred native events. Generated at build time. | +| [Notice delivery matrix](./notices.md) | Which notice channels each host supports and why the rest are unavailable. Generated at build time. | +| [Diagnostics reference](./diagnostics.md) | Every `AB` code family, trigger, severity, and recovery hint. Generated at build time from the repository contract. | +| [Runtime environment](./runtime-environment.mdx) | Node floors, path tokens, environment variables, `.env` layering, and durable state locations. | +| [Security](./security.mdx) | The credential, network, and trust boundaries. | +| [Limitations](./limitations.mdx) | What the framework does not currently do or prove. | +| [Type API](./api.mdx) | The generated symbol reference for every public export. | + +## Reading the diagnostics + +Every command reports structured diagnostics rather than prose errors. One diagnostic is a stable +`AB` code, a severity, a message, and usually a `sourcePath` and a `recovery` hint. For the +diagnostic-gated commands — `build`, `prepack`, `validate`, `doctor`, `install`, and `dev` — only +an **error** severity makes the command exit nonzero; warnings and infos never gate a build, a +validation, or a dev rebuild. `eval` and `inspect` have an extra, non-diagnostic reason to exit +`1`: a failing or inconclusive trial, or an invalid model — see the +[CLI exit codes](./cli.mdx#exit-codes). + +The full code catalog is the [Diagnostics reference](./diagnostics.md), rendered at build time +from the repository's +[`docs/diagnostics.md`](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/diagnostics.md). + +| Family | Area | +| --- | --- | +| `AB30xx` | Skill documents: Markdown parsing and rendered-skill compilation. | +| `AB40xx`–`AB47xx` | Plugin metadata, normalized model invariants, hooks, MCP, scripts, assets, package build, and the bundler escape hatch. | +| `AB48xx`–`AB49xx` | Route graph, state, layout, and provider conventions: route module discovery, `config` extraction, shared layouts (`AB4830`–`AB4832`), and the compiled command surface. | +| `AB5000` | General CLI and adapter failures. | +| `AB60xx` | Built-artifact validation, including host schema documents and referenced files. | +| `AB700x`–`AB7013` | Host installation and the npm prepack gate. | +| `AB7xxx` | Project preparation and development rebuilds, plus the read-only Doctor at `AB7300`–`AB7320`. | +| `AB8xxx` | Development server configuration and Workbench routes. | +| `AB9xxx` | Eval selection, harnesses, and persisted runs. | diff --git a/website/docs/en/reference/limitations.mdx b/website/docs/en/reference/limitations.mdx new file mode 100644 index 000000000..fb737fb2c --- /dev/null +++ b/website/docs/en/reference/limitations.mdx @@ -0,0 +1,62 @@ +--- +description: 'Known agent-bundle limitations: unmigrated pre-0.1 durable records, loopback-only development, native harness requirements, inferred Codex activation evidence, and unverified comparison facets.' +--- + +# Limitations + +Stated plainly, so that a gap is never mistaken for a bug. + +## Durable records + +Development snapshots and exports written by pre-0.1 builds — before the unversioned +durable-record cutover — are **not migrated**. Rebuild artifacts and discard those preview Eval and +Playground records before upgrading. Current readers reject the superseded shapes instead of +guessing at compatibility. + +## The development server + +The Workbench binds to loopback only and is a foreground development session, not a hosted +service. + +## Native harnesses + +Native Claude and Codex harnesses require those CLIs to be installed and signed in. A missing, +incompatible, or unauthenticated CLI is reported as a **harness failure**, which is distinct from a +plugin failure. Their live smoke tests are opt-in and are not part of an ordinary test run. + +Semantic grading requires a native Claude harness and a signed-in Claude Code session; the +deterministic and Codex selections are refused when it is configured. + +## Evidence that cannot be observed + +Codex exposes no authoritative Skill-activation event, so Codex activation evidence is `inferred` +and is never reported as `observed`. + +Comparison facets that a run did not record — semantic grader identity, host CLI version, +invocation — are labeled **unverified** rather than assumed aligned. + +## What the contract matrix does not prove + +No matrix boundary proves browser App HTML or artifact-rebuild replay. In-memory runs and compiled +artifacts without event routes report runtime identity as honestly `not-applicable`. + +The packed boundaries cannot load project route modules — source may be deleted and verified +absent — so serialized-round-trip, compat-probe, and version-skew checks are reported +`not-applicable` with a stated reason rather than silently skipped. `host-install` reports +module-backed checks the same way, because loading project modules would cross back into the +source and build tree. + +When an advertised input schema declares `additionalProperties: false`, plain `z.object` tool +routes may still strip unknown keys without a protocol failure. The negative-inputs check records +that tolerance when other generated negatives still prove rejection paths. + +## Host validation + +Neither Codex nor Cursor publishes a plugin-validation developer verb, so their emitted documents +are checked against vendored pinned schemas, and host schema generators are treated as a drift +signal rather than a substitute contract. See +[Artifact validation](../guide/distribution/validation.mdx). + +## Rendering + +Raw HTML, JSX/MDX, and Mermaid in Skill Markdown are inert in the Workbench renderer. diff --git a/website/docs/en/reference/runtime-environment.mdx b/website/docs/en/reference/runtime-environment.mdx new file mode 100644 index 000000000..8e22d2176 --- /dev/null +++ b/website/docs/en/reference/runtime-environment.mdx @@ -0,0 +1,88 @@ +--- +description: 'The runtime contract for generated agent-bundle executables: the Node.js floor, host path tokens, AGENT_BUNDLE environment variables, .env precedence, and durable state locations.' +--- + +# Runtime environment + +## Node.js + +The compiler and the Workbench need **Node.js 22.19 or later**; generated executables target +**Node.js 22.12 or newer** unless `runtime.node` raises that floor. Both are stated in +[Installation](../guide/start/installation.mdx), and the selected executable floor is recorded as +`runtime.node` in the artifact manifest. + +## Host path tokens + +Generated documents reference the plugin root through the token each host substitutes, and the +compiler writes the right spelling per target rather than assuming one: + +| Host | Plugin root | Plugin data | +| --- | --- | --- | +| Claude Code | `${CLAUDE_PLUGIN_ROOT}` | `${CLAUDE_PLUGIN_DATA}` | +| Cursor | `${CURSOR_PLUGIN_ROOT}` | — (rejected: no documented host equivalent) | +| Codex | `${PLUGIN_ROOT}` (hook commands only) | — (rejected: no documented host equivalent) | +| portable | `${PLUGIN_ROOT}` | `${PLUGIN_DATA}` | + +Substitution is **positional, not global**. Codex's `${PLUGIN_ROOT}` is the command root of its +generated hook commands and nothing else: the Codex MCP runtime interpolates no path tokens, so a +plugin-root token in an MCP `command`, `args`, or `env` value is accepted only when it leads the +value and the server's `cwd` is the plugin root, in which case the compiler rewrites it to a +`./`-relative path under `cwd: "./"`; an embedded token, a token without that `cwd`, or any +plugin-data or workspace-root token is a build error. Claude Code substitutes `${CLAUDE_PLUGIN_ROOT}` and +its siblings in Skill and agent content, hook and monitor commands, MCP servers, and LSP servers +only — never in `settings.json`, which is why `claude.settings` rejects path tokens outright. +Cursor's pinned loader has its own substituted-field table, and a token outside it reports +`AB6028` at build time and `AB7320` from Doctor. + +## Environment variables + +| Variable | Read by | Meaning | +| --- | --- | --- | +| `AGENT_BUNDLE_PLUGIN_ROOT` | Generated executables | The durable-state anchor. Overrides the built-in fallback. | +| `AGENT_BUNDLE_AGENT_API_TOKEN` | `agent-bundle dev` | The bearer token the Agent API requires before it can be enabled. | +| `AGENT_BUNDLE_HOOK_HOST` | Generated hook wrappers | Pins the declared host explicitly instead of detecting it. | +| `AGENT_BUNDLE_HOOK_SIMULATION` | Generated hook wrappers | `1` marks a simulated invocation; the Workbench hook playground sets it. | +| `AGENT_BUNDLE_NATIVE_HOST_CONTRACTS` | Contributor test suites | `1` compares the installed host CLI contract. | +| `AGENT_BUNDLE_NATIVE_CLAUDE_SMOKE` | Contributor test suites | `1` runs the signed-in Claude native smoke. | +| `AGENT_BUNDLE_NATIVE_CODEX_SMOKE` | Contributor test suites | `1` runs the signed-in Codex native smoke. | +| `AGENT_BUNDLE_WORKBENCH_API_PROXY` | Contributor HMR (`packages/workbench/scripts/dev.mjs`) | The running Agent Bundle foreground server URL required before Workbench UI HMR starts. See [Developer Workbench](../guide/development/workbench.mdx). | + +The three native-smoke opt-ins exist because those runs need a real signed-in CLI; they are never +part of an ordinary test run. + +## .env precedence under `mcp run` + +`agent-bundle mcp run` composes the launch environment in three layers, lowest priority first: + +1. The **generated server's own env**, with path tokens already resolved. +2. The **`.env` file layer**. +3. The operator's real `process.env`, so an exported variable always wins. + +The `.env` layer is the conventional Rsbuild set at the project root (the runtime calls this the +workspace root) — `.env`, `.env.local`, `.env.`, `.env..local` — unless +`--env-file ` replaces it with exactly the files you name, or `--no-env` removes it. The two flags are mutually exclusive. Files are read into a +scratch object, so the real `process.env` is never mutated. + +## Durable state + +Durable state resolves to `$AGENT_BUNDLE_PLUGIN_ROOT/state`, falling back to the artifact root, or +to `./.agent-bundle/state` for CLI bins. Only a `workspace-durable` state definition uses the +SQLite driver; other lifetimes use the in-memory driver and keep nothing on disk. + +Under `mcp run`, plugin-root anchors in **env values** expand to the project root by default, +not the artifact: the artifact is an ephemeral build product there, and anchoring durable state on +it would fragment that state on every rebuild. Pass `--plugin-root ` pointing at the +artifact target root for a byte-faithful rehearsal of a copied-artifact launch. + +A per-server state directory uses the server name when it is a single safe segment; any other +name becomes a content-addressed `server-` segment, so a name like `../shared` can never +traverse out of the state root. + +Default state projection budgets are 5,000 ms per commit, 262,144 bytes per event, 1,048,576 bytes +of state, and 100,000 revisions. `agent-bundle inspect --state` reports the resolved driver, +lifetime, durable location, and budget source for each definition. + +## Next + +- [Security](./security.mdx) — the credential and network boundaries around these processes. +- [CLI](./cli.mdx) — the flags referenced above. diff --git a/website/docs/en/reference/security.mdx b/website/docs/en/reference/security.mdx new file mode 100644 index 000000000..391226445 --- /dev/null +++ b/website/docs/en/reference/security.mdx @@ -0,0 +1,83 @@ +--- +description: 'The agent-bundle security boundaries: no provider credentials, loopback-only development surfaces, the Agent API bearer token, installer restraint, and inert Skill Markdown.' +--- + +# Security + +Most of what follows is a refusal rather than a feature. These are contracts the framework holds +to, not defaults you can relax with a flag. + +## Provider credentials + +**This package never accepts, requests, injects, or persists a model-provider API key.** + +Native Claude and Codex harnesses use an already installed, signed-in CLI, and they run *after* +provider-key environment variables have been removed. Credential-shaped material in the `evals` +configuration block is rejected outright rather than ignored: Agent Bundle reuses the host CLI's +existing session, and there is no fallback path that reads an API key from configuration. + +Each Codex trial sets a temporary `CODEX_HOME` and copies only the installed CLI's opaque +`auth.json` into it. Your normal Codex home, configuration, and installed-plugin state are not +used as trial state and are left unchanged. The contributor smoke commands remove provider +API-key and credential-shaped environment values and fail if the selected host's normal home state +changes. + +Native authenticated smokes are excluded from default and ordinary local test runs, and the +trusted self-hosted CI workflow that runs them uses an existing subscription session with **no +workflow secrets**. + +## The development server + +`agent-bundle dev` binds to **loopback only** and is a foreground development session, not a +hosted service. The browser is not a trusted input source: it never supplies a command, a working +directory, a native model, or a credential, and browser-supplied native models and credentials are +refused. Operations are trusted-local only. + +Raw HTML, JSX/MDX, and Mermaid in Skill Markdown are **inert** in the Workbench renderer. A Skill +document is content to display, not markup to execute. + +## The Agent API + +The Agent API is off by default. When enabled it is an authenticated Streamable HTTP MCP endpoint +mounted at `/mcp` on the same loopback foreground server, with exactly thirteen fixed, ordered +tools. + +- Startup fails **before serving** if the endpoint is enabled without + `AGENT_BUNDLE_AGENT_API_TOKEN`. +- The fixed token is read once and is never logged, persisted, or returned. +- Standard `Authorization: Bearer` authentication is required. +- Clients may omit `Origin`; a supplied origin must match the foreground URL exactly. +- When the endpoint is disabled it is **absent**, not merely unauthorized. + +The operational details are in [Developer Workbench](../guide/development/workbench.mdx). + +## Installation + +Installing a package never mutates a host's plugin state: **no npm lifecycle performs an +installation**. The standalone `install.mjs` copies into `~/.cursor/plugins/local/` without +overwriting collisions, is idempotent for identical content, refuses version or content +collisions, never invokes `sudo`, and never changes `PATH`. + +`agent-bundle doctor` is read-only by construction. It inventories durable state by directory +entry and filesystem metadata only — it never opens a database — and it never repairs anything. + +## Artifact integrity + +Content-addressing is the trust anchor for a distributed bundle. Every emitted file carries a +SHA-256 in `agent-bundle.manifest.json`, so a modified artifact fails validation rather than +installing quietly, and the `host-install` proof level fails closed when source, built-artifact, +installed-artifact, and running-process versions disagree. + +Path containment is enforced in both directions: a declared logo that escapes the deploy tree +(`AB6025`), a symlink resolving outside a Cursor bundle (`AB6028`), an installed local plugin whose +symlink escapes `~/.cursor/plugins/local` (`AB7320`), an eval `runsDir` that leaves the project +root, and an MCP server name that would traverse out of its state root are all rejected. + +## What is not claimed + +The package publishes no example RSC provider and no host credentials. The native Claude and Codex +eval harnesses (`runClaudeTrial`, `runCodexEvalTrial`, `--harness claude|codex`) drive a CLI you +have already installed and signed in to; they carry no credentials of their own. Third-party notices, including the vendored MCP Inspector snapshot's license and +provenance, ship in the published package. + +See [Limitations](./limitations.mdx) for what the evidence surfaces do **not** prove. diff --git a/website/docs/en/reference/targets-artifacts.mdx b/website/docs/en/reference/targets-artifacts.mdx new file mode 100644 index 000000000..58270598b --- /dev/null +++ b/website/docs/en/reference/targets-artifacts.mdx @@ -0,0 +1,76 @@ +--- +description: 'What each agent-bundle target emits and the artifact manifest contract: file kinds, digests, source inputs, target schema pins, and recorded validation status.' +--- + +# Targets and artifacts + +The target table — which layout each target emits, and why the portable standard omits rules, +commands, and hooks — is in [Configuration model](../guide/authoring/index.mdx). The directory +layouts are in [Project structure](../guide/start/project-structure.mdx). This page is the +artifact contract those layouts have to satisfy. + +## Every target directory is distributable + +A built target directory is the unit of distribution: no packaging step follows the build. Each +one carries the components the host reads, a generated `INSTALL.md` written with the bundle's +real plugin and marketplace names, and the install surface that target requires. + +| Target | Marketplace manifests | Install surface | +| --- | --- | --- | +| `claude` | Local marketplace manifest. | `claude plugin marketplace add` + `claude plugin install`. | +| `codex` | Local marketplace manifest. | `codex plugin marketplace add` + `codex plugin add`. | +| `cursor` | Local marketplace manifest (`.cursor-plugin/marketplace.json`) when `marketplace: true`. | `install.mjs`. | +| `portable` | — | `install.mjs`. | +| `plugin` | Claude, Codex, and Cursor manifests, plus a generated `AGENTS.md`. | `install.mjs` and the host CLIs. | + +Artifact validation errors when a target is missing a required install-surface file, so a bundle +cannot ship without the installer its `INSTALL.md` promises. The npm pack inventory checks the +same paths (`AB7010`). + +## agent-bundle.manifest.json + +One manifest is emitted per artifact and is the input to every later integrity check — +`validate --artifact`, `prepack`, `doctor --from`, and the packed and installed-host proof levels. + +| Field | Contents | +| --- | --- | +| `producer` | `{ name: 'agent-bundle', version }`. | +| `project` | `configPath`, `configDigest`, `modelDigest`, `revision`, the validated `packageName` and `packageVersion` (absent for unpackaged development projects), and `sourceInputs`. | +| `files` | Every emitted file: `path`, `bytes`, `sha256`, `kind`, optional `mode`, and the `sourceInputs` it derives from. | +| `runtime` | `{ node }` — the selected generated-executable floor. | +| `targets` | Per target: `name`, `adapterRevision`, `observedVersion`, and the pinned `schemas` it was validated against. | +| `agentSkills` | The Agent Skills specification pin: `specification`, `sourceRevision`, `schemaSha256`. | +| `validation` | Recorded status for `source`, `artifact`, and each target. | + +`kind` is one of `bundle`, `copy`, `generated`, or `prebuilt` — a bundled entry, a byte-for-byte +copy, a compiler-generated document, or a prebuilt payload file. `sourceInputs` records each +input path with its own SHA-256 and, where relevant, whether it was executable. + +Because every file carries a digest, validation compares **real bytes** rather than checking that +a path exists, and a hand-edited generated file fails. That contract is what makes +`validate --artifact`, `mcp`, and `hooks` work against an artifact whose project sources have been +deleted. + +## Versions and revisions + +Four version axes are tracked separately and are expected to agree: + +- **Source** — the project's `package.json` release version. +- **Built artifact** — `project.packageVersion` in the manifest. +- **Installed artifact** — the manifest found under a host's installed root. +- **Running process** — the version a live MCP `initialize` reports. + +The `host-install` proof level records all four and fails closed when any is missing or differs. +`prepack` gates the first two plus normalized plugin metadata and host manifests (`AB7013`). + +Target rows also record an `adapterRevision` (monotonic, repository-owned) and an +`observedVersion` (the host version the capability evidence was recorded against). Neither is +hashed: Git already versions repository-owned content, and hashing it again inside the repository +causes churn on every table edit. Hash pins are reserved for vendored external content — host +document schemas under `src/adapters/schemas/*` with their `PROVENANCE.json`, the Agent Skills +schema pin, and emitted files and source inputs. + +## Next + +- [Artifact validation](../guide/distribution/validation.mdx) — the checks that read this manifest. +- [Runtime environment](./runtime-environment.mdx) — what the emitted executables assume at run time. diff --git a/website/docs/public/logo.svg b/website/docs/public/logo.svg new file mode 100644 index 000000000..da74c16ff --- /dev/null +++ b/website/docs/public/logo.svg @@ -0,0 +1,9 @@ + + agent-bundle + + + + + + + diff --git a/website/docs/zh/_nav.json b/website/docs/zh/_nav.json new file mode 100644 index 000000000..5fe1c9eda --- /dev/null +++ b/website/docs/zh/_nav.json @@ -0,0 +1,27 @@ +[ + { + "text": "指南", + "link": "/guide/start/", + "activeMatch": "/zh/guide/" + }, + { + "text": "参考", + "link": "/reference/", + "activeMatch": "/zh/reference/" + }, + { + "text": "示例", + "link": "/examples/", + "activeMatch": "/zh/examples/" + }, + { + "text": "参与贡献", + "link": "/contributing/", + "activeMatch": "/zh/contributing/" + }, + { + "text": "类型 API", + "link": "/api/", + "activeMatch": "/zh/api/" + } +] diff --git a/website/docs/zh/api/_meta.json b/website/docs/zh/api/_meta.json new file mode 100644 index 000000000..dccbc0d78 --- /dev/null +++ b/website/docs/zh/api/_meta.json @@ -0,0 +1,33 @@ +[ + "index", + { + "type": "dir", + "name": "modules", + "label": "模块" + }, + { + "type": "dir", + "name": "functions", + "label": "函数" + }, + { + "type": "dir", + "name": "classes", + "label": "类" + }, + { + "type": "dir", + "name": "interfaces", + "label": "接口" + }, + { + "type": "dir", + "name": "types", + "label": "类型别名" + }, + { + "type": "dir", + "name": "variables", + "label": "变量" + } +] diff --git a/website/docs/zh/contributing/_meta.json b/website/docs/zh/contributing/_meta.json new file mode 100644 index 000000000..a7d5eda49 --- /dev/null +++ b/website/docs/zh/contributing/_meta.json @@ -0,0 +1 @@ +["index"] diff --git a/website/docs/zh/contributing/index.mdx b/website/docs/zh/contributing/index.mdx new file mode 100644 index 000000000..d6901e8d2 --- /dev/null +++ b/website/docs/zh/contributing/index.mdx @@ -0,0 +1,123 @@ +--- +description: 'agent-bundle 的贡献者工作流:三道本地门禁、本地合并循环、Changesets、需要显式开启的原生宿主 smoke,以及文档站点。' +--- + +# 参与贡献 + +这一页是给**改动 agent-bundle 本身**的人看的。其中没有任何一项是编写插件的前提——插件作者只需要 +`agent-bundle` 与 Node.js,本页内容一概不需要。 + +仓库是一个 pnpm 工作区。这里以 Node.js 22.19 为下限,原因和框架本身以它为下限是同一个。 + +从全新检出开始: + +```sh +pnpm install +pnpm build +``` + +`pnpm build` 会构建其余脚本与每个示例都依赖的工作区包,因此全新安装之后先跑它。 + +## 三道门禁 + +| 命令 | 证明什么 | 何时运行 | +| --- | --- | --- | +| `pnpm check` | 本地交付门禁:构建、单元、路由单元、投影与集成测试、lint、类型检查。 | 开发过程中,以及每次推送之前。 | +| `pnpm check:release` | 与 `pnpm check` 并列的打包证据:`npm pack` dry run、发布审计,以及含脚手架模板矩阵的打包测试池。 | 改动打包、导出或已发布表面之前。 | +| `pnpm check:local-ci` | 完整的托管 CI 门禁——三个 Node 版本的 verify 矩阵,加上示例、发布与微 eval 作业——在并行的本地 worktree 中运行。 | 作为合并门禁。 | + +还有一些范围更小的脚本用于迭代:`pnpm test:unit`、`pnpm test:route-unit`、 +`pnpm test:projection`、`pnpm test:integration`、`pnpm lint` 与 `pnpm typecheck`。它们是更快的信号, +不是门禁。 + +## 本地合并循环 + +`pnpm check:local-ci` 存在的原因是托管 verify 腿很慢,而且在这个仓库里,本地绿灯才是分支合并的依据。 +它把每条腿放进一个隔离的 git worktree,钉在分支的 HEAD 提交上,各自拥有自己的 `node_modules` 与自己的 +临时目录根: + +1. 在分支的 HEAD 提交上运行 `pnpm check:local-ci`——未提交的改动**不**在覆盖范围内,运行器发现它们时 + 会给出警告。 +2. 如果门禁是绿的,这个分支就可以合并。 +3. 托管 CI 仍会在合并后的提交上运行,并继续充当异步的合并后安全网。如果它与本地运行结论不一致, + 以托管结果为准,并为这次合并补一个后续修复。 + +想快速迭代,`pnpm check:local-ci --current-node-only` 会在当前激活的 Node 上运行一条等价于 verify 的 +腿。它跳过 Node 矩阵以及示例、发布与微 eval 门禁,因此是快速信号,而不是合并门禁。当怀疑复用的腿 +worktree 已经陈旧时,用 `--fresh` 重建它们。 + +运行器按顺序从显式的 `AGENT_BUNDLE_LOCAL_CI_NODE_22` / `_24` / `_26` 覆盖项、`mise`、`~/.nvm`、 +当前进程中解析每条托管 Node 线(22.19.x、24.x、26.x),并在使用前对每个二进制做版本校验。缺少某条线时 +它会带上确切的安装命令失败,而不是悄悄用错运行时。腿的 worktree、逐步骤日志,以及“腿 × 步骤 × 状态 × +时长 × 测试普查”的汇总表都位于被 git 忽略的 `.worktrees/local-ci/` 目录下。 + +[`docs/local-ci.md`](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/local-ci.md) +是完整契约,包括本地门禁刻意不覆盖的那些内容。 + +## Changesets + +版本管理走 [Changesets](https://github.com/changesets/changesets)。对用户可见的改动,请在同一个提交里 +补上一条: + +```sh +pnpm changeset +``` + +选择受影响的包与版本级别,并从读者的角度描述这次改动——changeset 是发布说明的文案,不是提交信息。 +`pnpm version-packages` 应用待处理的 changeset,而发布会在 `changeset publish` 之前先运行 +`pnpm check:release`。私有工作区包,包括示例与本站点,不参与版本管理,也不打标签。 + +目前还没有任何东西发布到 npm。当前的发布通道是 pkg.pr.new 预览 tarball,见 +[预览包](../guide/distribution/preview-packages.mdx)。 + +## 原生宿主 smoke 需要显式开启 + +有些证据需要机器上已登录的 Claude 或 Codex CLI,因此它不可能成为门禁。这些 smoke 在本地需要显式开启, +在 CI 中被刻意跳过;运行它们的托管工作流只能通过 `workflow_dispatch` 触发,并且要指明它演练哪个宿主。 + +```sh +pnpm test:packed:native:claude +pnpm test:packed:native:codex +pnpm test:host-install:session:claude +``` + +测试矩阵中的其余部分都不需要凭据。`host-install` 保留一条无条件的确定性适配器模拟通道,可用的宿主 +二进制还会额外证明它们各自的公开安装路径,而 Cursor 会显式记录它那条不可用的非交互宿主会话表面, +而不是报告一个它没有挣到的通过。这条边界正是[测试](../guide/development/testing.mdx)中证明级别的全部 +意义所在:一个级别绝不会被当作更强级别的收据来报告。 + +依赖审查、包预览与发布工作流出于结构性原因只在托管侧运行——第一项要针对 pull request 的 diff 查询 +GitHub 的安全公告数据库,另外两项是发布的副作用,而不是检查。 + +## 文档站点 + +本站点是一个私有工作区包。请在仓库根目录运行: + +```sh +pnpm docs:site:dev # 带热更新的本地开发服务器 +pnpm docs:site:build # 类型检查、构建,并核实构建产物 +pnpm docs:site:preview # 提供已构建的站点 +``` + +`pnpm docs:site:build` 就是门禁,`Docs` 工作流会在每个 pull request 上运行它,并把 `main` 部署到 +GitHub Pages。遇到失效的站内链接、失效的锚点、缺失的图片,或某个页面只在一个语言下存在 +时,构建都会失败——最后这一条正是 `guide/`、`reference/`、`examples/` 与 `contributing/` 下每个英文页面 +都有一个结构相同的中文对照页的原因。TypeScript 示例在两个语言下保持一致;散文与代码注释需要翻译。 +生成的类型 API、宿主能力、事件、通知与诊断参考页在构建时由仓库中的事实来源渲染而来,只做镜像,不翻译。 + +## 仓库约定 + +有两条规则值得单独写出来,因为它们很容易被无意破坏: + +- **`examples/*` 是面向用户的产品,不是测试夹具。** 它们只能使用公开的 `agent-bundle` 导出与 + `workspace:*` 依赖,并在桌面视口下验收。开发者 Workbench 是一个仅面向桌面的应用。 +- **`repos/` 下随仓库携带的参考材料是只读的。** 不要编辑、格式化或从中导入。应用代码导入的是已发布的 + 包。 + +框架自身的契约与代码一起放在仓库中: +[Framework mode](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/framework-mode.md) +讲编写模型, +[Entry conventions](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/entry-conventions.md) +讲包构建, +[Diagnostics](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/diagnostics.md) +是 `AB` 代码目录。 diff --git a/website/docs/zh/examples/_meta.json b/website/docs/zh/examples/_meta.json new file mode 100644 index 000000000..281cd0775 --- /dev/null +++ b/website/docs/zh/examples/_meta.json @@ -0,0 +1 @@ +["index", "skills-starter", "hooks-and-scripts", "mcp-app", "audiobook-curator"] diff --git a/website/docs/zh/examples/audiobook-curator.mdx b/website/docs/zh/examples/audiobook-curator.mdx new file mode 100644 index 000000000..439ddcc4d --- /dev/null +++ b/website/docs/zh/examples/audiobook-curator.mdx @@ -0,0 +1,96 @@ +--- +description: '有声书策展器示例:一个由路由模块、请求上下文提供者、持久状态、MCP 到 CLI 投影与框架自有包构建组成的完整 agent-bundle 应用。' +--- + +# 有声书策展器 + +这不是对某一个表面的演示,而是一个完整的插件:一个真实的媒体管理应用,由 React Server Component 路由 +模块、请求上下文、持久状态、MCP 路由与 CLI 路由组装而成。一次构建产出一个生成的 stdio MCP 服务器、 +一个可安装的 CLI、一个 Skill,以及原生的 Claude Code 与 Codex 插件产物。它是框架自有包构建的参考 +消费者。 + +- **在仓库根目录运行:** `pnpm example:audiobook` +- **包名:** `@agent-bundle-example/audiobook-curator` +- **公开依赖:** `agent-bundle`(`workspace:*`)、`@agent-bundle/runtime`(`workspace:*`)、 + `@modelcontextprotocol/server`、`react`、`zod` +- **Target:** `claude`、`codex`,且 `marketplace: true` +- **需要:** Node.js 22.19 或更高版本,以及 `PATH` 上的 `ffprobe` 与 `ffmpeg` +- **源码:** [`examples/audiobook-curator`](https://github.com/ScriptedAlchemy/agent-bundle/tree/main/examples/audiobook-curator) + +可选功能会调用为其提供证据的外部工具:Audiobook Forge、位于所选 Python 环境中的 Audiolocate,以及带 +指定模型的 `whisper-cli`。跳过它们只会失去那些功能,而不影响构建。这个示例没有钩子。 + +## 它证明什么 + +- **路由树就是应用。** `agent-bundle.config.ts` 声明标识、Node 运行时、两个 target 与 MCP 到 CLI 的 + 投影。其余由文件约定发现:`src/mcp/curator/` 下的 16 个工具路由、1 个目录资源与 1 个策展提示词。 + 这里没有操作注册表,没有手写的 `src/mcp/curator.ts`,也没有逐操作的服务器选择器。 +- **一份编写好的表面,两种协议。** `routes.mcpCommands` 把全部 16 个 MCP 工具投影为 + `audiobook-curator curator `,因此编译后的图共有 32 条 CLI 命令:`src/cli/` 下 16 条编写好的, + 加上 16 条投影出来的。被投影的工具接受一个可选的 `--input ''`;只读工具直接运行, + 而可能产生变更的工具需要 `--yes`。 +- **表现层是共享的,不是复制的。** `src/components/` 是一份报告组件库,MCP 路由与渲染式 CLI 路由都 + 组合它,因此一个 MCP 工具和它的 CLI 对应命令不可能漂移成两套表现层。 +- **请求上下文是观察出来的,不是假定的。** 约定式的 `src/providers/library.ts` 在每个请求中探测 + `ffmpeg -version` 与 `ffprobe -version`,并连同探测时间一起发布工具可用性。目录资源通过 + `await agent()` 读取它,并渲染实时上下文或一个显式的不可用状态。 +- **持久状态有被挂载的身份。** `src/state.ts` 挂载工作区级持久状态 `audiobook-curator/shelf` 与三个 + 事件。如果状态未挂载,那个只读 MCP 工具与渲染式 `shelf` CLI 命令都会返回一个空的结构化书架和一条 + 显式的不可用提示,而不是失败。 +- **Suspense 变成 MCP 进度。** `audit_library` 先通过请求的 `context.progress` 报告进度,然后把它的 + 异步分析放到 React `Suspense` 之后,回退内容是一个 `Agent.Progress` 节点。生成的投影器会流式传输 + 进度状态,再用完成后的分析替换它,而最终的结构化收据不变。 +- **包构建归框架所有。** 配置没有声明 `bin`,也没有声明 `scripts`。`src/cli/` 下的路由命令编译为 + `dist/bin/audiobook-curator.js` 以服务 `package.json` 的 `bin`,而 `dist/index.js` 加声明文件服务 + `exports`。见 [CLI 与库包入口](../guide/authoring/package-entries.mdx)。 +- **`package.json` 是唯一的版本来源。** 配置没有声明 `plugin.version`;解析出的版本会流入项目上下文、 + 产物清单、`inspect` 输出、dev 状态,以及这个插件导入的 `agent-bundle/meta` 常量。 + +## 渲染式与普通 CLI 路由 + +七条编写好的 `.tsx` 命令渲染 Agent Document——`inventory`、`select`、`audible-search`、`convert`、 +`audit`、`library-audit` 与 `shelf`。交互式终端会就地更新它们报告的进度;管道输出则是一份最终的 +Markdown 文档。另外九条兼容性命令仍是普通的 `.ts` 路由。 + +两类命令都支持用 `--json` 选择机器输出,并输出一个经结果 schema 校验的 JSON 值加一个换行。对于渲染式 +命令,这个值是最终的规范 `Agent.Result` 值——绝不是 Markdown 表现,也绝不是中间的 Suspense 回退—— +因此某条命令改为渲染式时,收据的消费方不需要改动。 + +## 在工作区中开发 + +```sh +pnpm --filter @agent-bundle-example/audiobook-curator build +pnpm --filter @agent-bundle-example/audiobook-curator test +pnpm --filter @agent-bundle-example/audiobook-curator test:routes +pnpm --filter @agent-bundle-example/audiobook-curator typecheck +``` + +包内的 `pnpm check` 会运行 validate、build、typecheck 与两个测试池。一次 +`agent-bundle build --output artifact` 产出全部内容:`artifact/` 下完整的 Claude 与 Codex 输出——各宿主 +的插件元数据、Skill、打包后的 CLI 脚本与被生命周期包装的 MCP 服务器——以及 `dist/` 下的 npm 包。 + +想在不打包 tarball 的情况下试用构建好的 CLI,可以从任意已在 `PATH` 上的可写目录链接构建产物中的 bin: + +```sh +cd examples/audiobook-curator +ln -s "$(pwd)/dist/bin/audiobook-curator.js" ~/.local/bin/audiobook-curator +audiobook-curator --help +``` + +## 在 stdio 上运行服务器 + +```sh +cd examples/audiobook-curator +pnpm exec agent-bundle mcp run --server curator --target claude +``` + +该命令会从 Claude target 的 MCP 清单解析出生成的入口,并先构建一份临时产物;传入 +`--artifact artifact` 可改为复用 `pnpm build` 的输出。关闭 stdin 以 0 退出,Ctrl-C 以 130 退出, +每个服务器的状态持久化在 `.agent-bundle/mcp-run/claude/curator` 下。 + +## 安全边界 + +源文件不可变,规划阶段绝不改动媒体。转换会发布到独立的目标位置;元数据与章节替换必须显式 apply, +先写入同目录的暂存文件,校验暂存结果,然后原子重命名。JSON 收据拒绝音频后缀,也拒绝与媒体或证据输入 +发生冲突。网络响应体、进程输出、目录遍历与并发都是有界的,网络工作使用有界重试加调用方的取消信号, +而不是隐藏的超时。本地媒体进程没有挂钟期限;调用方取消与有界的 stdout、stderr 依旧强制生效。 diff --git a/website/docs/zh/examples/hooks-and-scripts.mdx b/website/docs/zh/examples/hooks-and-scripts.mdx new file mode 100644 index 000000000..7ba4a1468 --- /dev/null +++ b/website/docs/zh/examples/hooks-and-scripts.mdx @@ -0,0 +1,79 @@ +--- +description: '钩子与脚本示例:一个 session-start 钩子、两个输出脚本(其中一个带阻断性退出码)、持久的 Playground 轨迹,以及一次可逆的诊断演练。' +--- + +# 钩子与脚本 + +一次无需凭据的发布准备会话。它证明的是两个最容易手工写错的契约:生成的钩子包装器,以及框架为编写好的 +脚本套上的那层进程外壳。 + +- **在仓库根目录运行:** `pnpm example:hooks` +- **包名:** `@agent-bundle-example/hooks-and-scripts` +- **公开依赖:** `agent-bundle`(`workspace:*`) +- **Target:** `portable`、`codex`、`claude` +- **凭据:** 不需要——示例只读取 `release/` 下签入的 JSON +- **源码:** [`examples/hooks-and-scripts`](https://github.com/ScriptedAlchemy/agent-bundle/tree/main/examples/hooks-and-scripts) + +## 它证明什么 + +- **钩子是写成处理函数,而不是宿主文档。** `src/hooks/session-start.ts` 只是一个模块。构建会把它降级为 + 各宿主自己的钩子文档形状,并输出运行它的包装器。见[钩子](../guide/authoring/hooks.mdx)。 +- **两种脚本声明方式都在。** `verify-release` 按约定发布——`src/scripts/` 下任何未被声明占用的普通脚本 + 都会被发现——而 `detect-risk` 保持显式配置,因为它要把自己的 target 限制为 `portable`。示例故意让两种 + 方式都有覆盖。 +- **进程外壳属于框架。** 两个脚本都导出 `main` 并返回退出码。argv 处理、等待与退出码采纳都归生成的外壳 + 所有,因此一个非零返回值会变成真正的阻断性退出,而不是被吞掉的返回值。见 + [脚本与资源](../guide/authoring/scripts-assets.mdx)。 +- **输出的脚本自行解析自己的资源。** `assets: ['release/*.json']` 把发布清单与风险登记表复制进每个 + target,而每个输出脚本都相对自己所在的模块去读取它们——而不是相对 shell 的工作目录。 +- **失败的重建保留上一个可用产物。** 这就是下面那段可逆演练。 + +## 编写了什么 + +| 路径 | 是什么 | +| --- | --- | +| `src/hooks/session-start.ts` | `sessionStart` 处理函数,把发布会话导向那两项检查。 | +| `src/scripts/verify-release.ts` | 以清单为依据的打包检查,按约定被发现。 | +| `src/scripts/detect-risk.ts` | 风险登记表检查,显式配置以限制它的 target。 | +| `release/release-manifest.json` | 被作为资源复制的打包发布清单。 | +| `release/risk-register.json` | 阻断性检查读取的风险登记表。 | + +## 在 Workbench 中操作 + +1. **Overview** 把编写好的钩子与它的输出产物、演练轨迹和评估页面关联起来。它的状态就是“当前或已过期” + epoch 状态的权威来源。 +2. **Hooks** 默认选中 Claude 的 `sessionStart` 绑定,并带有已填充的内联规范 JSON,其中包含 + `"source": "workbench"`。运行这次模拟,然后用 **Replay saved simulation** 精确重放那份绑定到该 + epoch 的输入。 +3. **Playground** 默认是脚本执行、Claude target 与 `verify-release`。运行它并等待会话定稿:输出的脚本 + 读取自己模块旁边打包好的 `release/release-manifest.json`,并报告 2.4.0 版本已可打包。 +4. 把 target 换成 portable 并选择 `detect-risk`。它读取 `release/risk-register.json`,报告高严重级别的 + `REL-204`,以退出码 2 结束,并定稿一条持久的阻断性轨迹。 +5. **Logs** 可按生产者、级别、种类或上下文过滤这些生产者记录;打开某条记录即可查看原始细节。 + **Artifacts** 是输出文件与来源视图,而 **Comparisons** 只有在有两次记录的 eval 运行之后才会对齐结果。 + +## 可逆的诊断演练 + +签入的项目是健康的,所以要看到“上一个可用产物”的行为,就得故意把它弄坏。把 +`src/hooks/session-start.ts` 的函数体临时替换为一个语法不完整的处理函数,按 **Rebuild**,并等待 +**Failed** 这个已完成状态。Workbench 会报告新的诊断,同时继续提供上一个可用产物。恢复签入的处理函数, +再按一次 **Rebuild**,等到 **Idle**:一个新的活跃 epoch 会替换过期状态并清除该诊断。 + +不要把 Building 状态读成修复已完成——新的活跃 epoch 才是证据。 + +## 非交互检查 + +在仓库级 `pnpm build` 之后: + +```sh +cd examples/hooks-and-scripts +pnpm validate +pnpm build +``` + +`pnpm check` 是同一对“校验加构建”,只用一条命令。想在不打开 Workbench 的情况下检视输出的钩子包装器, +可以用命令行自身的钩子表面: + +```sh +pnpm exec agent-bundle hooks list --artifact artifact +``` diff --git a/website/docs/zh/examples/index.mdx b/website/docs/zh/examples/index.mdx new file mode 100644 index 000000000..ae452501d --- /dev/null +++ b/website/docs/zh/examples/index.mdx @@ -0,0 +1,81 @@ +--- +description: '可运行的 agent-bundle 示例:从 Skill 起步项目到完整媒体管理插件的四篇导览,外加两个进阶组合参考。' +--- + +# 示例 + +仓库中带有六个示例,它们是产品,而不是测试夹具。每个示例只使用公开的 `agent-bundle` 导出与 +`workspace:*` 依赖,都通过你自己也会用的那套公开命令行来构建与校验,并且都不需要 API key 或已登录的 +宿主就能进入有意义的状态。其中四个在这里有导览: + +| 示例 | 证明什么 | 运行 | +| --- | --- | --- | +| [Skills 起步项目](./skills-starter.mdx) | 按约定发现的 Skill,以及确定性的 eval 证据。 | `pnpm example:skills` | +| [钩子与脚本](./hooks-and-scripts.mdx) | 生成的钩子包装器与脚本进程外壳,包含一个阻断性退出码。 | `pnpm example:hooks` | +| [MCP App](./mcp-app.mdx) | 一个生成的 stdio MCP 服务器,加上一个交互式 MCP App 资源。 | `pnpm example:mcp-app` | +| [有声书策展器](./audiobook-curator.mdx) | 一个完整的路由树应用:MCP、投影出的 CLI、提供者与持久状态。 | `pnpm example:audiobook` | + +另外两个是进阶组合参考,由各自的 README 说明,这里不做导览: + +| 示例 | 证明什么 | +| --- | --- | +| [Worktree Proximity](https://github.com/ScriptedAlchemy/agent-bundle/tree/main/examples/worktree-proximity) | 一个根任务与两个位于关联 worktree 中的子智能体,通过持久通知协调。 | +| [RSC Agent Runtime](https://github.com/ScriptedAlchemy/agent-bundle/tree/main/examples/rsc-agent-runtime) | 一个可选启用的架构实验:一个 RSC 运行时同时服务钩子、MCP 工具与 MCP App 时间线。不是公开 API。 | + +## 如何运行 + +`pnpm example:*` 脚本要在仓库根目录运行。每个脚本先构建工作区,然后启动该示例的前台 +`agent-bundle dev` 服务器并打印回环 [Workbench](../guide/development/workbench.mdx) URL。传入 +`--open`(例如 `pnpm example: -- --open`)才会打开浏览器: + +```sh +pnpm install +pnpm example:skills +``` + +若需要非交互路径,`pnpm examples:check` 会运行每个示例包自己的 `check` 脚本——校验与构建,以及示例自带 +的类型检查与测试——既不启动 dev 服务器,也不打开浏览器: + +```sh +pnpm examples:check +``` + +在仓库级 `pnpm build` 构建过本地 `agent-bundle` 工作区依赖之后,每个示例包也直接暴露同样的公开命令。每个示例都有 +`validate`、`build` 与 `check`;四个导览示例与 Worktree Proximity 还有 `dev`,而 RSC Agent Runtime 演示则通过其测试与 +`eval:hosts` 驱动: + +```sh +cd examples/skills-starter +pnpm validate # agent-bundle validate +pnpm build # agent-bundle build +pnpm check # 校验并构建,不打开 Workbench +pnpm dev # agent-bundle dev +``` + +## 如何读一个示例 + +下面每个示例页面都记录同样的四件事:这个示例证明什么、它依赖哪些公开包、它在仓库根目录的运行命令, +以及源码链接。请把页面和源码对照着看——配置文件故意写得很短,因为 `src/` 约定承担了大部分结构。 + +在探索时,有两个习惯能让 Workbench 的结论值得信任: + +- **等待一个已完成的状态。** 重建报告 Idle 或 Failed 时才算结束。Building 仍在进行中,据此下判断 + 正是健康项目看起来像坏了的原因所在。 +- **把失败的重建理解为“上一个可用产物”的行为。** 失败的构建不会发布 epoch,因此 Workbench 会在报告 + 新诊断的同时继续提供之前发布的产物。恢复签入的源码并重建;新的活跃 epoch 才是修复证据。 + +[钩子与脚本](./hooks-and-scripts.mdx)正因如此专门带了一段有脚本可循、可逆的诊断演练——故意弄坏一个 +处理器,观察失败的重建如何保留上一个可用产物,恢复源码,再把新的 epoch 当作修复证据来读。 +[Skills 起步项目](./skills-starter.mdx)则针对过期的评测证据练习同一套“重建再恢复”的循环;其余导览只要求你 +在评判一次重建之前等到一个已完成的状态。 + +## 概念分别在哪里 + +| 示例中的表面 | 指南页面 | +| --- | --- | +| Skill 文档及其 `references/` 与 `assets/` | [Skills](../guide/authoring/skills.mdx) | +| 生命周期钩子 | [钩子](../guide/authoring/hooks.mdx) | +| 脚本与资源复制 | [脚本与资源](../guide/authoring/scripts-assets.mdx) | +| MCP 服务器与 MCP App | [MCP 服务器与 MCP App](../guide/authoring/mcp.mdx) | +| `bin`/`lib` 包构建 | [CLI 与库包入口](../guide/authoring/package-entries.mdx) | +| eval 套件、用例与 harness | [评测](../guide/development/evaluations.mdx) | diff --git a/website/docs/zh/examples/mcp-app.mdx b/website/docs/zh/examples/mcp-app.mdx new file mode 100644 index 000000000..d135d322c --- /dev/null +++ b/website/docs/zh/examples/mcp-app.mdx @@ -0,0 +1,95 @@ +--- +description: 'MCP App 示例:把一条服务就绪度工作流表达为生成的 stdio MCP 服务器、带类型的工具、交互式 MCP App 资源、Skill、钩子、脚本与确定性 eval。' +--- + +# MCP App + +一条服务就绪度工作流,只表达一次,却输出为宿主能触达的每一种表面:一个真实的本地 MCP 服务器、一个带 +类型的工具、一个交互式 MCP App 资源、一个 Skill、一个 session-start 钩子、一个夹具检查脚本,以及一个 +确定性 eval。当你想看清这些表面如何拼在一起、而不是单独研究其中之一时,就该读这个示例。 + +- **在仓库根目录运行:** `pnpm example:mcp-app` +- **包名:** `@agent-bundle-example/mcp-app` +- **公开依赖:** `agent-bundle`(`workspace:*`)、`@modelcontextprotocol/server`、 + `@modelcontextprotocol/ext-apps`、`zod`;浏览器端 App 测试还用到 `@rstest/browser`、`@rstest/core`、 + `@rstest/playwright` 与 `playwright` +- **Target:** `portable`、`codex`、`claude`——MCP App 资源仅保留在 portable +- **凭据:** 不需要——eval 与夹具检查只读取签入的数据 +- **源码:** [`examples/mcp-app`](https://github.com/ScriptedAlchemy/agent-bundle/tree/main/examples/mcp-app) + +## 它证明什么 + +- **MCP 入口约定。** `src/mcp/status.ts` 默认导出 `status` 服务器工厂,而配置没有声明服务器 `entry`——构建 + 通过 `src/mcp/.ts` 约定发现它。见 + [MCP 服务器与 MCP App](../guide/authoring/mcp.mdx)。 +- **生成的 stdio 生命周期不由你来写。** 构建会把那个工厂包进生命周期外壳:console 重定向到 stderr 的 + 守卫、信号处理、stdin EOF 退出、有界关停与心跳。 +- **MCP App 是编译出来的资源,不是被服务的页面。** 声明的 app 在 portable target 下编译为 + `mcp-apps/status.html`,并带有稳定的 `resourceUri`。Codex 与 Claude 保留各自的宿主产物,但不包含这份 + portable 的 App 资源。 +- **降级证据是一等公民。** `status` 服务器提供不可变的 `compiler` 与 `payments-api` 健康记录,而 + `payments-api` 故意返回降级的延迟数据。有意思的输出是一项被如实报告的失败检查,而不是一张全绿截图。 +- **浏览器表面自成一个证明级别。** `tests/browser-app/status-panel.browser.test.ts` 通过 + `agent-bundle/test/browser` 把生产编译出的 App HTML 挂载到产品桥接之上——也就是 + [测试](../guide/development/testing.mdx)中的 `browser-app` 级别。 + +## 编写了什么 + +| 路径 | 是什么 | +| --- | --- | +| `src/mcp/status.ts` | 按约定被发现的 `status` 服务器工厂,提供那两条健康记录。 | +| `views/status-panel.ts` / `views/status-panel.html` | 编译为 `mcp-apps/status.html` 的 MCP App 入口与模板。 | +| `src/skills/service-readiness/` | 做出服务就绪度判断所需的证据、检查与报告。 | +| `src/hooks/session-start.ts` | 把就绪度工作流加入兼容宿主的会话。 | +| `src/scripts/check-service-fixture.ts` | 在发布演练之前校验签入的编译器夹具。 | +| `evals/status.eval.ts` | 确定性的 `mcp-app-status` 套件及其 `status-is-healthy` 用例。 | + +## 在 Workbench 中操作 + +1. **Overview** 打开时是 Bundle 仪表盘。它的 Author、Build、Exercise 与 Evaluate 阶段把源码中的能力与 + 它的输出产物、运行时证据和 eval 结果连起来。 +2. **Skills** 默认选中 `service-readiness`;把它编写好的状态策略与就绪度报告资源,与生成输出及其显式 + eval 覆盖对比。**Hooks** 默认是一份已填充的 Claude `sessionStart` 规范输入。 +3. **Playground** 默认是脚本执行、Claude target 与 `check-service-fixture`。运行它并等待会话定稿:输出 + 的检查器会解析自己输出模块旁边打包好的状态夹具,因此它的成功与 shell 的工作目录无关。 +4. **Artifacts** 在选中 portable target 时,就是 `mcp-apps/status.html` 出现的地方。在存在两次 eval + 运行之前,**Comparisons** 会刻意显示 + `At least two recorded runs are needed before a comparison can be aligned.`——这是精确的空状态, + 不是错误。 +5. **MCP playground** 默认是 portable 与 `status` 服务器。打开会话、列出工具、选择 `show-status`、 + 选中 `payments-api` 并调用它。调用历史会显示降级摘要,以及标注了 Availability 与 P95 latency 的检查, + 其中后者失败。打开 App 预览:渲染出的面板通过 MCP Apps 桥接展示同一条记录,并带一个以文字标注的 + 琥珀色 `degraded` 指示。检视协议轨迹、使用 **Restart MCP session**,然后关闭、重置并重新打开会话, + 以演练整个生命周期。 +6. **Evals** 默认选中 `mcp-app-status`。运行 `status-is-healthy`,查看归属于 `service-readiness` 的那次 + 已完成且通过的试次。 + +如果你修改了某个源文件,请重建并等到 Failed 或 Idle 状态再判断结果;Building 状态仍在进行中。 + +## 非交互检查 + +在仓库级 `pnpm build` 之后: + +```sh +cd examples/mcp-app +pnpm validate +pnpm build +pnpm exec agent-bundle eval --case status-is-healthy --trials 1 +``` + +`pnpm check` 是不打开 Workbench 的“校验加构建”这一对。 + +## 在 stdio 上运行服务器 + +```sh +pnpm exec agent-bundle mcp run --server status --target portable +``` + +`mcp run` 会从 portable target 的 MCP 清单解析出生成的入口,并先构建一份临时产物;传入 +`--artifact artifact` 可改为复用 `pnpm build` 的输出。关闭 stdin 以 0 退出,Ctrl-C 以 130 退出, +每个服务器的状态持久化在 `.agent-bundle/mcp-run/portable/status` 下。 + +该命令默认会加载项目根目录的 `.env` 集合,包括所选 `--mode` 的变体。启动环境的优先级是清单 env, +然后是 `.env` 文件,最后是导出的操作者变量。可重复的 `--env-file ` 会替换那些约定文件, +`--no-env` 跳过它们,而 `--plugin-root ` 只用于复制产物后的演练。完整契约见 +[运行时环境](../reference/runtime-environment.mdx)。 diff --git a/website/docs/zh/examples/skills-starter.mdx b/website/docs/zh/examples/skills-starter.mdx new file mode 100644 index 000000000..01ebef87f --- /dev/null +++ b/website/docs/zh/examples/skills-starter.mdx @@ -0,0 +1,66 @@ +--- +description: 'Skills 起步项目示例:三个按约定发现的工程运维 Skill,配有确定性 eval 证据,且不需要任何凭据。' +--- + +# Skills 起步项目 + +一个只有 Skill 的插件。它证明:一个值得安装的捆绑包,除了自身标识与 target 之外可以什么都不声明—— +而 Skill 的质量仍然可以由记录下来的证据支撑,而不只是一句声称。 + +- **在仓库根目录运行:** `pnpm example:skills` +- **包名:** `@agent-bundle-example/skills-starter` +- **公开依赖:** `agent-bundle`(`workspace:*`) +- **Target:** `portable`、`codex`、`claude` +- **凭据:** 不需要——两个 eval 套件都是确定性的,只读取签入的夹具 +- **源码:** [`examples/skills-starter`](https://github.com/ScriptedAlchemy/agent-bundle/tree/main/examples/skills-starter) + +## 它证明什么 + +- **约定发现。** `agent-bundle.config.ts` 只声明插件与它的三个 target,别的什么都没有。每个 + `src/skills/*/SKILL.md` 目录都按约定被发现,因此配置从不列出任何 Skill。这正是 + [Skills](../guide/authoring/skills.mdx) 中描述的编写模型。 +- **一份源码,三种宿主布局。** 同一批编写好的文档被降级到 portable、Codex 与 Claude 三棵输出树中。 + Workbench 的 Source 与 Generated 视图会显示某个 target 究竟是复制还是改写了一份文档。 +- **确定性的 eval 证据。** eval 套件通过确定性 harness 针对签入夹具运行,因此某个 Skill 的覆盖是一次 + 记录下来的运行,而不是一句断言。覆盖被标记为*间接*,因为确定性 harness 无法观察宿主端的 Skill 激活。 + +## 编写了什么 + +| 路径 | 是什么 | +| --- | --- | +| `src/skills/incident-triage/` | 一次生产事故,从最初信号到止损、证据收集,直到可直接交接的进展更新。 | +| `src/skills/dependency-upgrade/` | 依赖升级规划,包含 API、运行时、灰度与回滚检查。 | +| `src/skills/release-review/` | 一次显式发布评审所需的证据、严重级别、流程与最终报告要求。 | +| `evals/release-readiness.eval.ts` | `release-readiness` 套件及其确定性用例 `release-artifact-is-ready`。 | +| `evals/engineering-operations.eval.ts` | `engineering-operations` 套件:`incident-handoff-is-actionable` 与 `upgrade-plan-has-rollback`。 | + +每个 Skill 都链接自己的 `references/` 清单或运行手册,以及一份可复用的 `assets/` 交接或规划模板, +因此 Skill 正文保持简短,长篇材料只在需要时才被加载。 + +## 在 Workbench 中操作 + +1. **Overview** 打开时是 Bundle 仪表盘:三个 Skill、生成的 target、构建健康状况,以及下一步值得做的 + 操作。 +2. **Skills** 列出 `dependency-upgrade`、`incident-triage` 与 `release-review`。浏览它们链接的清单与 + 报告模板,并按 target 在 Source 与 Generated 之间切换。 +3. **Artifacts** 默认选中 Claude target。切换 target 即可对比 portable、Codex 与 Claude 的输出树及其 + 来源信息。 +4. **Evals** 默认选中 `release-readiness` 套件。运行 `release-artifact-is-ready` 并查看通过的试次; + 它只消费签入的证据夹具。 +5. 想演练修复,就对发布策略做一处可逆修改,按 **Rebuild**,并等待 Failed 或 Idle 结果,而不是 Building + 状态。恢复签入的策略并重建。此时先前那次 eval 对改动后的构建已经过期——重新运行 + `release-readiness`,记录当前已修复状态下的证据。 + +## 非交互检查 + +在仓库级 `pnpm build` 之后: + +```sh +cd examples/skills-starter +pnpm validate +pnpm build +pnpm exec agent-bundle eval --case release-artifact-is-ready --trials 1 +``` + +`pnpm check` 就是不打开 Workbench 的“校验加构建”这一对。生成的输出落在 `artifact/`,其根契约是 +`artifact/agent-bundle.manifest.json`。`.agent-bundle/` 目录存放开发期状态,不是源码材料。 diff --git a/website/docs/zh/guide/_meta.json b/website/docs/zh/guide/_meta.json new file mode 100644 index 000000000..6dbd2a884 --- /dev/null +++ b/website/docs/zh/guide/_meta.json @@ -0,0 +1,22 @@ +[ + { + "type": "dir-section-header", + "name": "start", + "label": "开始" + }, + { + "type": "dir-section-header", + "name": "authoring", + "label": "编写" + }, + { + "type": "dir-section-header", + "name": "development", + "label": "开发" + }, + { + "type": "dir-section-header", + "name": "distribution", + "label": "分发" + } +] diff --git a/website/docs/zh/guide/authoring/_meta.json b/website/docs/zh/guide/authoring/_meta.json new file mode 100644 index 000000000..ae2a9e0d3 --- /dev/null +++ b/website/docs/zh/guide/authoring/_meta.json @@ -0,0 +1 @@ +["index", "skills", "hooks", "mcp", "scripts-assets", "package-entries"] diff --git a/website/docs/zh/guide/authoring/hooks.mdx b/website/docs/zh/guide/authoring/hooks.mdx new file mode 100644 index 000000000..28a66d2e9 --- /dev/null +++ b/website/docs/zh/guide/authoring/hooks.mdx @@ -0,0 +1,246 @@ +--- +description: '声明 agent-bundle 生命周期钩子:规范事件、工具选择器、处理器结果契约,以及钩子模拟。' +--- + +# 钩子 + +钩子是一个处理器模块,编译器会把它包装起来并注册到每个宿主原生的钩子文档中。你只需按规范事件分键 +声明一次,适配器就会把事件名、匹配器与结果形状翻译成所选宿主期望的样子。 + +## 声明一个钩子 + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + hooks: { + sessionStart: { handler: './src/hooks/session-start.ts' }, + }, + plugin: { + name: 'hooks-and-scripts', + description: 'Release preparation helpers.', + }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +每个事件都接受一个裸的处理器路径、一个条目对象,或者当同一事件需要多个处理器时,接受由两者组成的 +数组: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + hooks: { + beforeTool: [ + './src/hooks/audit.ts', + { + handler: './src/hooks/guard-writes.ts', + targets: ['claude'], + timeout: 10, + tools: ['file.write', 'shell'], + }, + ], + }, + plugin: { description: 'Guarded tooling.', name: 'guarded' }, + targets: ['portable', 'claude'], +}); +``` + +| 字段 | 含义 | +| --- | --- | +| `handler` | 处理器模块,或一个 `prebuilt` 标记,指向已声明 payload 中某个已构建好的文件。 | +| `targets` | 把钩子限制到特定 target。默认是所有支持钩子的所选 target。 | +| `timeout` | 原生钩子超时,**单位为秒**。省略则使用所选宿主的默认值。 | +| `tools` | 决定钩子何时触发的工具选择器。 | +| `args` | 额外的命令参数。只有预构建处理器接受参数,且只接受 shell 安全的字符串。 | + +## 规范事件 + +| 事件 | 触发时机 | +| --- | --- | +| `sessionStart` | 会话开始时。 | +| `beforeTool` | 工具调用派发之前。 | +| `afterTool` | 工具调用返回之后。 | +| `stop` | 智能体即将停止时。 | +| `agentStart` | 子智能体启动时。 | +| `agentStop` | 子智能体停止时。 | +| `workspaceOpen` | 工作区被打开时。请以[事件路由](#事件路由)(`src/events/workspace/open.tsx`)声明它,而不是配置钩子:没有任何 target 会映射这种朴素钩子形式,因此配置声明的 `workspaceOpen` 在每个宿主上都是构建错误——包括 Cursor,它只通过路由形式支持该事件。 | + +不实现某个事件的宿主,就不会收到对应的钩子;输出的文档保持诚实,而不是发明一个等价物。 + +## 工具选择器 + +`tools` 接受规范选择器——`shell`、`file.read`、`file.write`、`mcp`、`agent`——在宿主有对应原生匹配器时 +翻译过去。Cursor 映射全部五个;Claude Code 没有 `agent`;Codex 没有 `agent` 也没有 `file.read`。钩子上的每个 +选择器都必须在每个所选 target 上可映射,因此以 `codex` 为 target 的 `tools: ['file.read', 'shell']` 会导致构建 +失败,尽管单独的 `shell` 本可以工作。[事件与钩子矩阵](../../reference/events.md)渲染了这张匹配器表。`tools` 同时接受写成 `:` 的显式宿主原生选择器,例如 `claude:WebSearch` 或 +`codex:view_image`,这类选择器只会贡献给该宿主的原生匹配器。 + +选择了工具的钩子必须让每个所选 target 都至少留下一个可用的选择器。一个只限定 `claude:WebSearch` +却同时以 `codex` 为 target 的钩子会导致构建失败,而不是输出一份匹配器为空、永远不会触发的 Codex 文档。 + +## 处理器契约 + +处理器模块默认导出一个函数,它接收事件负载并返回结果。不返回任何内容等同于继续: + +```ts +// src/hooks/session-start.ts +interface SessionStartEvent { + readonly cwd?: string; + readonly sessionId?: string; + readonly source?: string; + readonly transcriptPath?: string; +} + +export default (event: SessionStartEvent) => ({ + additionalContext: `Run verify-release from ${event.cwd ?? process.cwd()} before publishing.`, + outcome: 'continue' as const, +}); +``` + +生成的包装层会在把结果投影为宿主原生输出之前先校验它。只接受四个键,并且每一个都会被检查: + +| 键 | 契约 | +| --- | --- | +| `outcome` | `continue`、`deny` 或 `stop`。 | +| `reason` | 非空字符串,仅在拒绝 `beforeTool`、`stop` 或 `agentStop` 钩子时有效。拒绝其中之一却不给出原因会失败。 | +| `additionalContext` | 追加到智能体上下文的字符串。 | +| `updatedInput` | 用于替换待执行调用的输入。 | + +逐事件的限制是被强制执行的,而不是「写在文档里、希望有人遵守」: + +- `sessionStart`、`afterTool` 与 `agentStart` 不能拒绝、停止或替换输入。 +- `beforeTool` 不能停止,也不能在拒绝的同时替换输入。 +- `stop` 只接受 `continue`,或带原因的 `deny`。 +- `agentStop` 不能停止父流程或替换输入;在 Codex 上也不能追加上下文。 + +未知的键、错误的类型,或违反上述任一规则,都会让钩子以清晰的错误消息失败,而不是被投影成一份行为 +异常却毫无声响的宿主文档。 + +## 预构建处理器 + +自行掌控编译的项目可以把钩子指向已声明 payload 中某个已构建好的文件: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + hooks: { + afterTool: [{ + args: ['--host', 'claude'], + handler: { prebuilt: './dist/runtime/hook/index.js' }, + targets: ['claude'], + tools: ['file.write'], + }], + }, + payload: { runtime: { source: './dist/runtime', targets: ['claude'] } }, + plugin: { description: 'Self-compiled runtime.', name: 'prebuilt-plugin' }, + targets: ['claude'], +}); +``` + +预构建钩子输出的原生命令形如 `node "/" `——一条配置声明取代了逐宿主手工 +维护的 `hooks/hooks.json`。预构建钩子按原生钩子文档的方式打包:它们不编译包装层,也不会出现在可模拟 +的钩子索引中。 + +## 事件路由 + +配置声明的钩子是紧凑形式。第二种编写形态是**事件路由**:`src/events/` 下的一个文件,其路径就是它处理的 +规范事件族(`src/events/tool/before.tsx`、`src/events/stop.tsx`)。它像 MCP 工具路由一样,是一个异步 +默认导出的 Server Component,再加一个可静态提取的 `config` 导出: + +```tsx +// src/events/tool/after.tsx +import { Agent } from '@agent-bundle/runtime'; +import type { AgentEventRouteConfig, AgentEventRouteProps } from 'agent-bundle'; + +export const config = { + runtime: 'standalone', // 'shared' 表示在生成的 MCP 服务器进程内渲染 + targets: ['claude', 'codex'], + timeoutMs: 30_000, // 在宿主自身原生截止时间之内的预算 + tools: ['file.write'], // 规范选择器 -> 各宿主的原生匹配器 +} satisfies AgentEventRouteConfig; + +export default async function AfterFileEdit({ canonical, native, signal }: AgentEventRouteProps) { + // canonical.provenance = { host, hostContractRevision, nativeEvent, source: 'native' } + return ( + + {`Recorded an edit reported by ${canonical.provenance.host}.`} + + ); +} +``` + +`canonical` 是框架推导出的跨宿主身份——`event`、由事件、target 与原生载荷哈希而来的 `idempotencyKey`、 +`observedAt`、一个 `sequence`,以及记录触发宿主与原生事件名的 `provenance`。`native` 是经过校验的宿主 +信封的冻结快照。`canonical` 中没有任何伪造:宿主未报告的轴保持不可用。 + +路由通过它渲染出的文档作答。`Agent.Context` 文本成为宿主的附加上下文通道,`Agent.Result` 的 `value` 可以 +携带 `{ outcome: 'continue' | 'deny', reason?, updatedInput? }`。投影按事件、按宿主进行,非法组合在任何 +内容到达宿主之前就会抛错:`session/end`、`compact/after`、`tool/failure` 与 `workspace/open` 在所有宿主上 +都只可观察;被拒绝的 `tool/before` 在 Claude 与 Codex 上成为 `hookSpecificOutput.permissionDecision`, +在 Cursor 上则是 `{ permission: 'deny', … }`;被拒绝的 `stop` 成为 `{ decision: 'block', reason }` 或 +Cursor 的 `followup_message`。每个宿主支持哪些事件族,见生成的[事件与钩子矩阵](../../reference/events.md)。 + +事件路由可以触达全部二十个规范事件族(`session/end`、`prompt/submit`、`compact/before`、 +`permission/request`……);配置声明的 `hooks` 只覆盖上面列出的七个。 + +### 线上到底传了什么 + +两种形态共享输出的 `hooks/hooks.json` 接线,并且都编译成宿主以 +`node "${CLAUDE_PLUGIN_ROOT}/hooks/.mjs"`(或宿主自己的根令牌)调用的包装层。配置声明的处理器 +在该包装层进程内运行。`runtime: 'shared'` 的事件路由则转发给生成的 MCP 服务器进程内的常驻运行时, +因此钩子与工具共享状态: + +1. **宿主 → 包装层(stdin)。** 宿主写入一个 JSON 信封。包装层以 1 MiB 的硬上限流式读取 stdin,只解析 + 恰好一个值,并按宿主、按事件校验它(`session_id`、`transcript_path`、`cwd`、工具字段……)。任何 + 不匹配都以非零退出。 +2. **包装层 → 运行时(IPC)。** 一条按换行分隔的请求——`protocolVersion`、`artifactEpoch`、`event`、 + `hostContractRevision`、`target` 与校验过的 `native` 信封——经由每用户的 Unix socket(Windows 上为 + 命名管道)发送。socket 目录为 `0700`,socket 为 `0600`,端点哈希绑定了产物 epoch、target 与产物的 + 安装目录,因此两份安装绝不会共享运行时。请求与 `timeoutMs`(默认 5000 ms)竞速。 +3. **运行时渲染。** 服务端拒绝 epoch 不匹配与畸形消息,构造 `canonical` props,并在请求上下文内通过 + react-server worker 渲染路由组件。 +4. **运行时 → 包装层。** 一条 JSON 回复:`{ status: 'ok', output }`,或 `{ status: 'error', code }`, + 其中 `code` 为 `epoch-mismatch`、`invalid-message` 或 `runtime-failed`。 +5. **包装层 → 宿主(stdout)。** 宿主原生的响应。对 `tool/before`,即便路由没有渲染任何决定,包装层也 + 总会给出显式的允许/拒绝决定(除非路由拒绝,否则为允许),以宿主自己的字段名表达;沉默只保留给只可 + 观察的事件族。 + +失败默认是关闭的。唯一的回退是同时编译了独立形态的路由上的 `fallback: 'standalone'`,而且它**只**在 +`runtime-unavailable`(没有活的 socket)时触发。`runtime-timeout`、`epoch-mismatch`、`invalid-message` +与 `runtime-failed` 都以非零退出——过期的代码绝不会作答,也绝不会输出伪造的响应。 + +`runtime: 'standalone'` 的路由把它的模块打进包装层自身:同样的规范身份、同样的投影、没有共享的进程状态。 +`AB4817` 会拒绝这样的路由:它需要共享运行时,但在某个 target 上没有生成的 MCP 入口承载它,也没有独立 +回退。 + +## 检查与模拟 + +钩子是「构建通过」最没有说服力的表面,因此输出的包装层可以被直接运行: + +```sh +npx agent-bundle inspect --root . --hooks +npx agent-bundle hooks list --artifact artifact --target claude +npx agent-bundle hooks simulate --artifact artifact --target claude \ + --hook session-start-session-start-7ab7e8a5 \ + --input '{"sessionId":"local","transcriptPath":"/tmp/transcript.jsonl","cwd":"/tmp/project","source":"startup"}' +``` + +`--hook` 必须是 `hooks list` 打印出的精确 `name`(或 `id`)——规范化会把它推导为 +`--`,因此像 `session-start` 这样的裸事件名不会匹配任何钩子。 +`session-start-session-start-7ab7e8a5` 正是本页开头那段声明所产生的名称;哈希覆盖事件、处理器路径、 +targets、tools、timeout 与 args,因此声明的任何改动都会得到新名称——要复制的永远是 `hooks list` +为你的构建打印出的那一个。 +载荷是**规范**输入;包装层会把它转换为宿主的原生信封并按宿主的方式校验,所以 Claude 的 +`sessionStart` 模拟需要 `sessionId`、`transcriptPath`、`cwd` 与 `source`(工具事件还需要 +`toolName`、`toolInput` 与 `toolUseId`)。 + +`hooks simulate` 运行的是真实输出的包装层——正是宿主将要执行的那个文件——所以你看到的结果就是宿主 +会得到的结果。开发者 Workbench 提供同样的 playground,并展示每次运行的原始 stdout、stderr 与结果。 + +## 下一步 + +- [MCP 服务器与 MCP App](./mcp.mdx) —— 另一种可执行表面。 +- [脚本与资源](./scripts-assets.mdx) —— 钩子或 Skill 可以调用的代码。 diff --git a/website/docs/zh/guide/authoring/index.mdx b/website/docs/zh/guide/authoring/index.mdx new file mode 100644 index 000000000..1341d54a2 --- /dev/null +++ b/website/docs/zh/guide/authoring/index.mdx @@ -0,0 +1,133 @@ +--- +description: 'agent-bundle 配置模型:defineConfig、每个顶层字段,以及配置与约定之间的边界。' +--- + +# 配置模型 + +项目根目录下的 `agent-bundle.config.ts` 就是全部的声明式表面。它是一个小而扁平的对象,保存项目标识、 +target 选择,以及任何单个路由文件都无法拥有的策略。所有结构性的内容——存在哪些 Skill、存在哪些 MCP +路由、发布哪些脚本——都来自 `src/` 约定,除非你在这里覆盖它。 + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { description: 'Evidence-backed project tools.', name: 'my-plugin' }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +`defineConfig` 是一个恒等辅助函数:它的存在是为了给对象加上类型,而不是变换它。 + +## 项目标识 + +`plugin` 声明宿主所看到的插件身份: + +| 字段 | 含义 | +| --- | --- | +| `name` | 宿主原生的插件 slug。绝不是 npm 包名。 | +| `description` | 一句话说明,宿主会展示它,它也会写入生成的清单。 | +| `logo` | 相对项目根目录的图片路径,会复制到支持该字段的宿主产物中。 | +| `version` | **已废弃。** 发布版本号属于 `package.json`。 | + +`package.json` 对发布标识具有权威性。声明的 `plugin.version` 与之不一致时会报告 `AB4008` 警告; +而任何地方都没有版本号的发布构建会被直接拒绝(`AB4013`),而不是打包开发期回退值。插件代码通过 +`agent-bundle/meta` 读取自身标识,而不是手工维护一个版本模块。 + +## Targets + +`targets` 选择构建要输出的产物布局: + +| Target | 输出什么 | +| --- | --- | +| `plugin` | 一份多宿主捆绑包,`.claude-plugin/`、`.codex-plugin/` 与 `.cursor-plugin/` 三份清单共享同一套组件目录。 | +| `claude` | Claude Code 插件布局。 | +| `codex` | Codex 插件布局。 | +| `cursor` | Cursor 插件布局。 | +| `portable` | [Agent Plugins 开放标准](https://agent-plugins.org)(规范 1.0.0)。Cursor、Codex、VS Code、GitHub Copilot、Kiro 与 ChatGPT 都是原生客户端。 | + +可移植标准只打包 Skills 与 MCP 服务器,因此规则、命令与钩子在该 target 上是诚实地不可用,而不是被 +悄悄丢弃。Claude Code 只能通过 CLI 转译消费该标准,这正是它仍需要专属 target 的原因。 + +## 完整表面 + +| 字段 | 负责 | +| --- | --- | +| `plugin` | 项目标识。必填。 | +| `targets` | 要输出哪些产物布局。 | +| `skills` | 显式的 Skill 路径或 glob,取代 `src/skills/*` 约定。见 [Skills](./skills.mdx)。 | +| `hooks` | 按规范事件分键的生命周期钩子处理器。见[钩子](./hooks.mdx)。 | +| `mcp` | MCP 服务器声明及其 MCP App。见 [MCP 服务器与 MCP App](./mcp.mdx)。 | +| `scripts` | 稳定输出名到脚本入口的映射。见[脚本与资源](./scripts-assets.mdx)。 | +| `assets` | 显式资源条目,取代根 `assets/` 约定。 | +| `bin` / `lib` | npm 包构建。见[包入口](./package-entries.mdx)。 | +| `routes` | 进阶的路由图策略:`routes.servers` 逐服务器模式、`routes.cli`,以及 `routes.mcpCommands` 投影。 | +| `output` | `distPath` —— 产物根目录的位置。 | +| `runtime` | `node` —— 生成可执行文件的最低 Node.js 版本。 | +| `payload` | 按字节打包的预构建目录树。 | +| `state` | 设为 `false` 可禁用 `src/state.ts` 约定。 | +| `marketplace` | 设为 `true` 时额外输出 Cursor 可选的 `.cursor-plugin/marketplace.json`。Claude Code 与 Codex 无论如何都会输出各自的本地 marketplace 清单。 | +| `evals` | Eval 套件发现(`include`)、运行目录(`runsDir`)与可选的 `semanticGrader`。见[评估](../development/evaluations.mdx)。 | +| `dev` | 仅用于开发、绝不会进入产物的设置,例如 `agentApi`。 | +| `tools` | 唯一的打包器逃生舱。 | + +宿主作用域的扩展键——`claude`、`codex`、`cursor`、`portable`——由各个 target 适配器通过声明合并贡献,因此 +宿主专属取值留在自己的适配器中,而不会泄漏进编译器核心。普通项目完全不需要任何扩展键。 + +## 宿主作用域声明 + +每个宿主键都是可选的,其中的每个字段也都是可选的。`claude` 与 `codex` 扩展自共享的 +`AgentBundleHostConfig`,它唯一的字段 `nativeHooks` 指向一份由项目编写、target 原生的钩子文档 +(`hooks.json`),适配器会校验它并与编译出的钩子合并。各适配器拥有的字段如下: + +| 键 | 字段 | 落点 | +| --- | --- | --- | +| `claude`(`ClaudeHostConfig`) | `bin`、`channels`、`defaultEnabled`、`dependencies`、`displayName`、`lspServers`、`marketplace`、`metadata`、`monitors`、`outputStyles`、`settings`、`themes`、`userConfig`、`workflows` | `bin`、`outputStyles` 与 `workflows` 是被复制到插件根同名约定目录的项目目录;`channels`、`dependencies`、`defaultEnabled`、`displayName`、`metadata` 与 `userConfig` 经校验后写入 `.claude-plugin/plugin.json`(`channels` 必须指向输出的 `.mcp.json` 中的服务器);`marketplace` 丰富生成的 marketplace 条目;`monitors` 与 `themes` 属于实验特性,分别输出 `monitors/monitors.json` 与每个键一份的 `themes/.json`;`lspServers` 与 `settings` 见下文。 | +| `codex`(`CodexHostConfig`) | `apps`、`author`、`homepage`、`interface`、`keywords`、`license`、`marketplace`、`repository` | `author`、`homepage`、`keywords`、`license` 与 `repository` 是 `.codex-plugin/plugin.json` 中的发布者元数据;`interface`(品牌色、分类、能力、composer 图标、默认提示、开发者名称……)会合并覆盖编译器生成的安装表面默认值;`apps` 把已注册的 MCP 连接 id 映射进根目录的 `.app.json` 兼容文档;`marketplace` 编写输出的 `.agents/plugins/marketplace.json` 条目——`category`(默认为 interface 的分类)、`displayName`(默认为插件名)以及 `policy`,其中 `installation` 取 `AVAILABLE`、`INSTALLED_BY_DEFAULT` 或 `NOT_AVAILABLE`,`authentication` 取 `ON_INSTALL` 或 `ON_USE`;来源始终是本地插件根目录,任何其他键都会报错,而不是被悄悄输出。 | +| `cursor`(`CursorHostConfig`) | `author`、`category`、`homepage`、`keywords`、`license`、`minClientVersions`、`publisher`、`repository`、`tags` | 叠加到生成的 `.cursor-plugin/plugin.json` 上的清单元数据,每个字段都被固定的 Cursor 插件 schema 接纳。`author` 是封闭的 `{ name, email? }` 对象(没有 `url`);`minClientVersions` 把客户端标识映射为严格的 `X.Y.Z` semver 字符串,例如 `{ cursor: '3.13.0' }`,其他任何写法都是错误。Cursor 没有文档化的 `nativeHooks` 表面,因此 `cursor` 不扩展 `AgentBundleHostConfig`。 | +| `portable`([`PortableManifestConfig`](../../api/interfaces/index.PortableManifestConfig.md)) | `author`、`extensions`、`homepage`、`keywords`、`license`、`repository` | 叠加到输出的根 `plugin.json` 上的 Agent Plugins 1.0.0 §5.4 元数据;`extensions` 把反向域名的客户端命名空间映射为不透明对象载荷(§8)。省略的字段不会出现在清单中。 | + +Cursor 插件的其余一切都从跨宿主模型推导。[宿主参考](../../reference/hosts.md)中的宿主能力表 +记录了每个被固定的宿主版本实际认可其中哪些表面。 + +有两个 Claude Code 表面值得细看,因为它们的契约比名字所暗示的更窄: + +- **`claude.lspServers`** —— 由 `claude` target 以及 `plugin` 的 Claude 那一半输出为插件根目录的 + `.lsp.json`。路径 token 只在 `command`、`args`、`env` 与 `workspaceFolder` 中展开。agent-bundle + 不包含语言服务器二进制文件,请单独安装它,以确保所声明的命令位于 `PATH` 上。Codex、Cursor 与 + 可移植格式不会收到这份配置。 +- **`claude.settings`** —— 输出为插件根目录的 `settings.json`,Claude Code 会在插件启用时应用它。 + 被固定的契约只支持 `agent` 与 `subagentStatusLine`;任何其他键都会被拒绝,而不是发出一个 + Claude Code 会悄悄忽略的默认值。这里不展开任何路径 token,因为 `settings.json` 不在宿主的占位符 + 替换表中。在插件 `agents/` 组件仍被推迟期间,声明 `agent` 还会触发一条警告:被引用的 agent 必须 + 通过其他方式抵达插件根目录,例如预构建 payload。 + +## 运行时下限 + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { description: 'Evidence-backed project tools.', name: 'my-plugin' }, + runtime: { node: '24.0' }, + targets: ['portable'], +}); +``` + +生成的可执行文件默认以 Node.js 22.12 及以上为目标。`runtime.node` 抬高这个下限——它永远无法降低—— +所选下限会以 `runtime.node` 记录在产物清单中。 + +## JSX 意味着渲染 + +结构存在于配置与约定中;JSX 只出现在真正需要渲染的地方。一个可执行路由就是一个 async 默认导出的 +Server Component:它完成工作并返回 `Agent.*` 节点,并且只有在需要 host、session、actor、workspace、 +capability 或 state 上下文时才调用 `await agent()`。不存在公开的 `execute`/`render` 分裂,普通 `.ts` +路由也绝不会被包进 React 行为里。 + +## 可编写的表面 + +- [Skills](./skills.mdx) —— Markdown Skill、它们的资源,以及渲染式 Skill 源码。 +- [钩子](./hooks.mdx) —— 七个规范生命周期事件与工具选择器。 +- [MCP 服务器与 MCP App](./mcp.mdx) —— 生成式路由服务器、手写 stdio 入口与浏览器 App。 +- [脚本与资源](./scripts-assets.mdx) —— 产物脚本与静态文件。 +- [包入口](./package-entries.mdx) —— `bin`、`lib`、路由式 CLI 与打包器逃生舱。 diff --git a/website/docs/zh/guide/authoring/mcp.mdx b/website/docs/zh/guide/authoring/mcp.mdx new file mode 100644 index 000000000..fdcb446df --- /dev/null +++ b/website/docs/zh/guide/authoring/mcp.mdx @@ -0,0 +1,284 @@ +--- +description: '在 agent-bundle 中以生成式路由模块或手写 stdio 入口编写 MCP 服务器,并了解 MCP App 与插件根目录环境锚点。' +--- + +# MCP 服务器与 MCP App + +MCP 服务器是插件中可执行的那一半。agent-bundle 提供两种编写方式:**生成式路由模块**,其中文件路径 +就是工具的身份;以及**手写 stdio 入口**,由你自己构造服务器,框架只负责它的进程生命周期。 + +## 生成式路由服务器 + +在 `src/mcp//` 下每个路由放一个模块: + +```text +src/mcp/curator/ +├── tools/status.tsx +├── resources/catalog.tsx +├── prompts/triage.tsx +└── apps/status-panel.tsx +``` + +路径提供身份:`src/mcp/curator/tools/status.tsx` 就是 `curator` 服务器的 `status` 工具。服务器的存在 +不需要任何声明。 + +每个可执行模块导出静态 `config`、它的 schema,以及一个 async 默认 Server Component: + +```tsx +// src/mcp/curator/tools/status.tsx +import React from 'react'; +import type { ToolConfig, ToolRouteProps } from 'agent-bundle'; +import { Agent, agent } from '@agent-bundle/runtime'; +import { z } from 'zod'; + +export const config = { + annotations: { readOnlyHint: true }, + description: 'Read runtime status.', +} satisfies ToolConfig; +export const inputSchema = z.object({ verbose: z.boolean().optional() }).strict(); +export const resultSchema = z.object({ status: z.literal('ready') }).strict(); + +export default async function Status({ input, signal }: ToolRouteProps) { + if (signal.aborted) throw new DOMException('aborted', 'AbortError'); + if (input.verbose) await agent(); + const result = { status: 'ready' as const }; + return ( + + Runtime is ready. + + ); +} +``` + +编译器静态读取 `config`,只把 schema 与实现导入生成的入口,装配 `runAgentRequest`,并从路由图推导出 +真实的 MCP 服务器。每次调用都经由一个常驻的内部 Flight 分发器渲染,并把最终的 Agent Document 降级为 +合法的 MCP 输出。Flight 是生成运行时内部的实现传输层——绝不是面向宿主的公开线上协议,原始 Flight +字节也永远不会跨越 MCP 线路。 + +`ToolConfig` 与 `ToolRouteProps` 都是公开类型: + +```ts twoslash +import type { ToolConfig } from 'agent-bundle'; + +export const config = { + annotations: { readOnlyHint: true }, + description: 'Read runtime status.', +} satisfies ToolConfig; +``` + +只有在路由确实需要上下文时才调用 `await agent()`。该句柄暴露本次调用,以及 `host`、`session`、 +`actor`、`workspace` 四个身份轴。每个轴都是*被观察到的*:传输层知道时会发布一个 `available` 取值及 +其来源,不知道时则发布带有类型化原因的 `unavailable`。裸 stdio 既不提供 session id 也不提供 HTTP +actor 认证,因此这两个轴保持诚实的不可用,而不是被伪造出来。 + +## 共享布局 + +`src/layout.tsx` 是每个渲染式路由外层的组合点——页面框架中 `layout.tsx` 的思路应用到 Agent Document 上。 +它默认导出一个接收 `{ children, route, signal }` 的组件,并在 `children`(路由渲染出的元素)外层渲染 +`Agent.Result`。`src/mcp//layout.tsx` 嵌套在它之内,只作用于一个生成式服务器;组合顺序是根布局、 +服务器布局、路由。生成式 MCP 工具、资源与提示、渲染式 `src/cli/**` 命令、投影的 MCP 命令以及渲染式 +`src/scripts/*.tsx` 都会被包裹。事件路由是宿主协议响应,浏览器 App 路由是浏览器构建,二者都不会被包裹。 + +```tsx +// src/layout.tsx —— 使用者需要编写的全部布局 +import { Agent, type AgentLayoutProps } from '@agent-bundle/runtime'; +import React from 'react'; + +export default function Layout({ children, route }: AgentLayoutProps) { + return ( + + {children} + + ); +} +``` + +没有 `value` 的 `Agent.Result` 是一个容器:运行时在解码时把它与路由自己的 `` +合并,因此路由保留其结果值、`structuredContent` 与内容,布局只添加共享的外壳——一个标题、末尾的 +`Agent.Context` 说明、文档元数据。`metadata` 对象按键合并,容器一方优先;由于 MCP 投影器把根元数据暴露为 +结果的 `_meta`,声明了元数据的布局会改变 `_meta`,没有声明的则保持原样。`route` 是编译期身份(`id`、 +`kind`、`name`,MCP 类型还有 `serverId`),`signal` 是请求中止信号,`await agent()` 在布局中的用法与在 +路由中完全一致。 + +路由的元素在布局链渲染**之前**解析,因此抛错的路由仍然让整次渲染失败(CLI 退出码 `1`、MCP 传输失败), +而不是被降级为布局外壳之下的 boundary 错误;代价是布局无法在 `children` 周围流式输出 `Suspense` 回退。 +默认导出不是函数、或导出了仅属于路由的 `config`/`inputSchema`/`resultSchema` 的布局是 `AB4830`;同一作用域 +下 `.ts` 与 `.tsx` 并存是 `AB4831`;其服务器没有声明任何工具、资源或提示路由的服务器布局是 `AB4832`,而固定为 +`custom`、`command` 或 `remote` 的服务器会完全跳过其布局。route-unit 与 projection 两个测试层级组合同一条 +布局链,因此 `renderRoute('tool:...')` 与 `invokeMcpTool(...)` 证明的是组合后的文档;直接传给 `renderRoute()` +的模块不会组合任何布局。 + +## 手写 stdio 入口 + +在配置中声明、但未指定 `entry`、`command` 或 `url` 的服务器,会识别约定的 `src/mcp/.ts` +模块。当它**默认导出一个服务器工厂函数**时,框架会把它放在生命周期外壳之下运行: + +```ts +// src/mcp/curator.ts — the whole stdio entry a consumer writes +import { createRscMcpServer } from '@agent-bundle/runtime/plugin'; + +import { application } from '../application.js'; + +export default () => createRscMcpServer(application, 'curator'); +``` + +生成的外壳按顺序提供:在消费者模块求值*之前*把 console 重定向到 stderr、调用工厂函数、为协议帧恢复 +原始的 `process.stdout.write`、构造并连接传输层、SIGINT 退出码 130、SIGTERM 退出码 143、stdin EOF +退出码 0(以便客户端重新拉起)、传输层关闭退出码 0、对卡死传输层的五秒有界关停竞态,以及 stderr 上 +的心跳与活动日志(五分钟间隔、六十秒活动节流,并以服务器名标注)。 + +这层保护之所以重要,是因为 stdout 承载着 JSON-RPC 帧:任何被导入模块中一次走神的 `console.log` 都会 +破坏协议流。 + +自行连接的入口——在顶层构造并连接传输层、且没有默认导出的模块——保持现有行为,逐字节不变。源码校验 +会报告信息级的 `AB4730` 提示,建议升级为工厂函数写法;它永远不是错误。 + +同一套生命周期对手写入口也是公开 API: + +```ts +import { redirectConsoleToStderr, runStdioServer } from 'agent-bundle/mcp-entry'; +``` + +## 在配置中声明服务器 + +当你需要约定无法表达的东西时再声明服务器——不同的入口路径、target 限制、额外环境变量,或者一个你 +并不编译的命令式或远程服务器: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + mcp: { + servers: { + curator: { entry: './src/mcp/curator.ts', transport: 'stdio' }, + remote: { transport: 'streamable-http', url: 'https://example.com/mcp' }, + }, + }, + plugin: { description: 'Library curation tools.', name: 'curator' }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +| 字段 | 含义 | +| --- | --- | +| `entry` | 要编译的源码模块,或一个 `prebuilt` 标记,指向已声明 payload 中某个已构建好的文件。 | +| `command` / `args` / `cwd` | 要启动的外部进程,用以替代编译入口。 | +| `url` / `headers` | 通过 `streamable-http` 访问的远程服务器。 | +| `transport` | `stdio` 或 `streamable-http`。 | +| `env` | stdio 服务器的额外环境变量。 | +| `targets` | 把服务器限制到特定 target。 | +| `apps` | 注册到该服务器上的浏览器 MCP App。 | + +## 插件根目录环境锚点 + +每个输出的 stdio MCP 服务器入口都带有 `AGENT_BUNDLE_PLUGIN_ROOT` 环境变量,其值是插件安装根目录在 +该 target 上的原生写法:Claude Code 上是 `${CLAUDE_PLUGIN_ROOT}`、可移植格式上是 `${PLUGIN_ROOT}`、 +Cursor 上是 `${CURSOR_PLUGIN_ROOT}`、Codex 上是 `./`,并相对入口的插件根 `cwd` 解析。Codex 没有路径 +token 插值,因此没有插件根工作目录的 Codex stdio 服务器会省略该锚点;由源码构建(`entry:`)的服务器 +在每个 target 上都一定有它。 + +**请以这个锚点、而不是进程工作目录来解析持久化状态与随包资源。** Claude Code 目前从宿主自身的工作 +目录启动 stdio 服务器,并忽略任何 stdio `cwd` 字段(其占位符表不包含 `cwd`)。因此当工作目录是规范 +插件根——也就是标准的由源码构建(`entry:`)场景——Claude 适配器会**省略** `cwd`,改为在第一个参数前 +加上 `${CLAUDE_PLUGIN_ROOT}/` 使入口路径变为绝对路径,并同时注入环境锚点。这个规范的插件根 `cwd` 是 +Claude 唯一接受的带 token 取值;其他任何携带路径 token 的 `cwd` 都会被拒绝,只有显式编写、且不含 token 的 +`cwd` 才会原样透传。 + +服务器自身的 `env` 条目优先于注入值,因此声明 `env: { AGENT_BUNDLE_PLUGIN_ROOT: ... }` 会替换该锚点。 +变量名有对应导出,消费者代码永远不必硬编码它: + +```ts twoslash +import { pluginRootEnvAnchor } from 'agent-bundle'; + +const readPluginRoot = (env: Record): string | undefined => + env[pluginRootEnvAnchor]; +``` + +## MCP App + +MCP App 是一个浏览器表面,编译为自包含 HTML 并作为资源注册到生成的服务器上。约定位置是 +`src/mcp//apps/*.{ts,tsx}`,其中必须提供静态 `config.resourceUri`。给文件名加 `_` 前缀即可 +退出。 + +在配置中声明 App 可以为它指定显式的 HTML 模板与 target 限制: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + mcp: { + servers: { + status: { + apps: { + status: { + entry: './views/status-panel.ts', + resourceUri: 'ui://mcp-app-example/status.html', + targets: ['portable'], + template: './views/status-panel.html', + }, + }, + }, + }, + }, + plugin: { description: 'A service-readiness assistant.', name: 'mcp-app-example' }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +已编译的 App 通过 `agent-bundle/mcp-apps` 提供给服务器代码,编译器会为本地 MCP 服务器替换该子路径。 +在 agent-bundle 编译之外导入它会抛出错误,而不是返回一个空注册表——把不受支持的边界显式化,而不是在 +运行时悄悄失败。 + +声明在*预构建*服务器上的 App 仍属于开发期表面:Workbench 会实时编译它,而构建假定 payload 已经在 +提供该资源。 + +## 服务器模式 + +当目录约定不该生效时,`routes.servers.` 可以让某个服务器退出路由生成——手写服务器用 +`custom`、外部进程用 `command`、通过 URL 访问的服务器用 `remote`。包含冲突恢复在内的完整模式契约见 +[Entry conventions](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/entry-conventions.md)。 + +## 运行与检查 + +```sh +npx agent-bundle inspect --root . --routes +npx agent-bundle mcp list --artifact artifact --target claude --server curator +npx agent-bundle mcp invoke --artifact artifact --target claude --server curator \ + --tool status --input '{"verbose":true}' +npx agent-bundle mcp run --artifact artifact --target claude --server curator +``` + +`mcp run` 在前台执行一个已构建的 stdio 服务器:它从该 target 的 MCP 清单中解析出生成入口(其文件名带有 +服务器名称的摘要),通过 target 适配器展开路径 token,加载项目根目录的 `.env` 集合,并转发子进程的退出码。不带 +`--artifact` 时会先构建一个临时产物。 + +## 实时宿主 MCP 代理 + +在开发期间,宿主可以在 `agent-bundle dev` 重建其背后的生成服务器时,始终保持一个 stdio MCP 进程连接 +不断。把宿主的 MCP 服务器命令配置为: + +```json +{ + "command": "agent-bundle", + "args": [ + "dev", + "proxy", + "--root", + "/absolute/path/to/plugin", + "--server", + "tools" + ] +} +``` + +该代理通过项目的开发锁发现 loopback 服务器,并连接到位于 `/mcp/host/` 的稳定 Streamable +HTTP 端点。`--target` 默认为 `portable`,`--url` 可覆盖发现过程。重建成功时会保持 stdio 连接不断、 +把新调用路由到当前 epoch、让已受理的调用在其原始 epoch 上完成,并转发 MCP 目录变更通知。若 epoch 或 +开发服务器消失,代理会以 MCP 错误以及 `AB8024` 或 `AB8025` 诊断失败关闭。 + +该端点刻意不做认证,因为开发服务器只绑定 loopback,绝不会暴露到本机之外。 + +## 下一步 + +- [脚本与资源](./scripts-assets.mdx) —— 产物中非协议类的可执行文件。 +- [包入口](./package-entries.mdx) —— `bin`、`lib` 与路由式 CLI。 diff --git a/website/docs/zh/guide/authoring/package-entries.mdx b/website/docs/zh/guide/authoring/package-entries.mdx new file mode 100644 index 000000000..d73d80621 --- /dev/null +++ b/website/docs/zh/guide/authoring/package-entries.mdx @@ -0,0 +1,231 @@ +--- +description: '框架自有的 npm 包构建:bin 与 lib 条目、可执行封套、路由式 CLI、agent-bundle/meta,以及唯一的打包器逃生舱。' +--- + +# CLI 与库包入口 + +agent-bundle 之于智能体插件,正如 Rslib 之于库:一份 `agent-bundle.config.ts`、一个 CLI、由框架拥有 +的入口生命周期,以及一个被祝福的、通往打包器的逃生舱。输出宿主产物的同一份配置,也负责 npm 包构建, +因此同时作为 CLI 或库发布的插件不需要第二份打包器配置。 + +## `bin` 与 `lib` + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + bin: { 'my-plugin': './src/cli.ts' }, + lib: { dts: true, entry: './src/index.ts' }, + output: { distPath: 'artifact' }, + plugin: { description: 'A CLI plus a plugin.', name: 'my-plugin' }, + targets: ['portable', 'claude'], +}); +``` + +| 配置 | 输出 | 说明 | +| --- | --- | --- | +| `bin: { '': './src/cli.ts' }` | `dist/bin/.js` | 自执行的 ESM 包,带 `#!/usr/bin/env node` shebang 与可执行位。 | +| `lib: { entry: './src/index.ts', dts: true }` | `dist/.js` 以及 `dist/**/*.d.ts` | 单入口 ESM profile,node target,es2022 语法。 | + +配置沉默时,`src/cli.ts` 与 `src/index.ts` 这两个约定会补齐它们。配置总是胜出,而 `bin: false` / +`lib: false` 则用于退出。 + +由于包输出位于 `dist/`,宿主产物必须放在别处:命令行默认的产物根目录是 `artifact/`,而在带有包入口的项目上把 +`output.distPath` 或 `--output` 指向 `dist` 就是 `AB4706`。`dist` 是强制忽略的目录:包输出 +永远不会进入项目源码快照,也不会进入 Skill 与资源发现。 + +输出会被暂存并原子发布,其 provenance——字节数、SHA-256,以及排序后的项目相对源码输入——会像产物文件 +一样在构建结果中报告。 + +### 声明文件 + +`lib.dts` 默认为 `true`。声明生成会从项目中解析 `typescript`,因此请把它加为 devDependency。它把 lib +入口所在的源码目录编译为独立的 program:编译选项通过 `extends` 来自项目的 `tsconfig.json`,`rootDir` +固定为入口所在目录,并且只纳入该子树——测试文件永远不会让包构建失败,也不会污染它。声明文件平铺落在 +`dist/` 下,每个源码模块一个 `.d.ts`。 + +`lib` profile 刻意做得很薄。需要多格式矩阵(UMD、多入口、逐格式 tsconfig)的包已经超出这个 profile, +它真正想要的是 Rslib。那是唯一一种仍然保留第二份打包器配置的情形,而且是主动选择的。 + +## 可执行封套 + +一个 `bin` 条目——或一个产物脚本——只要其模块导出了 `main`,或默认导出一个函数,就会获得生成的进程 +封套: + +```ts +// src/cli.ts — the whole CLI entry a consumer writes +export const main = async (argv: readonly string[]): Promise => { + // ... + return 0; +}; +``` + +封套会 await `main(process.argv.slice(2))`,把数值返回值作为进程退出码,并让逃逸的 rejection 走 +Node 的顶层失败路径(堆栈打到 stderr,退出码 1)。没有 `main` 导出的自执行模块则逐字节直接打包。 + +## 路由式 CLI + +`src/cli/**` 表面会编译成一个由框架生成的可执行文件,取代手写的分发器,也取代该项目的 `src/cli.ts` +bin 约定。嵌套即身份:`src/cli/library/audit.ts` 以 ` library audit` 运行。 + +```ts twoslash +// src/cli/inspect.ts — the whole command a consumer writes +import type { CliRouteConfig, CliRouteProps } from 'agent-bundle'; +import { z } from 'zod'; + +export const config = { + description: 'Inspect a bounded source tree without changing it.', + positionals: ['root'], +} satisfies CliRouteConfig; + +export const inputSchema = z.object({ + maxFiles: z.number().int().min(1).max(256).optional(), + root: z.string().min(1), +}).strict(); + +export const resultSchema = z.object({ scanned: z.number().int() }).strict(); + +export default async function inspect({ input, signal }: CliRouteProps) { + signal.throwIfAborted(); + return { scanned: input.maxFiles ?? 0 }; +} +``` + +编译器把 `inputSchema` 静态投影到 argv 上,生成分层帮助(每一层都有 `--help`,根层有 `--version`), +并通过与其他 bin 相同的打包器合成流程输出 `dist/bin/.js`。运行时,外壳解析命令路径、 +解析并强制转换 argv、用模块自身的 schema 校验、在带类型的 Agent 请求上下文中执行默认函数、向 stdout +写出一行规范 JSON,并确定性地映射退出码: + +| 退出码 | 含义 | +| --- | --- | +| `0` | 成功;在 `config.exitCode: 'result'` 下则取结果的 `exitCode`。 | +| `1` | 执行失败。 | +| `2` | 用法或输入失败。 | +| `130` / `143` | SIGINT / SIGTERM,它们同时会抵达路由的 `AbortSignal`。 | + +`.tsx` 命令路由把默认函数换成一个具有相同 props 的 async 默认 Server Component,并通过运行时分发器 +针对同级的 `dist/bin/-flight.mjs` worker 渲染。它由此获得 +[脚本与资源](./scripts-assets.mdx)中描述的四种输出模式。路由式 CLI 项目需要把 `@agent-bundle/runtime` +作为依赖,因为生成的可执行文件通过它装配请求上下文。 + +退出方式:`bin: false`、`routes.cli: 'conventional'`,或给某一段路径加 `_` 前缀。 + +### 宿主产物中的路由式 CLI + +包 bin 只能到达安装了 npm 包的用户,而 hook、Skill 与脚本是随宿主产物一起交付的。因此构建还会把同一张 +编译后的命令图发射进每个所选 target,路径为 `/bin/.mjs`(若有任一命令需要渲染, +则再加上 `bin/-flight.mjs`)。每个内置 target 都发布了准入它的 `cli` 能力。产物 bin 是一个 +自包含的 ESM 模块,没有 shebang 也没有可执行位——像 `scripts/*.mjs` 一样以 +`node /bin/.mjs ` 运行。帮助、argv 解析、输出模式、退出码与信号都与 +包 bin 一致;唯一的区别是,在宿主未提供 `AGENT_BUNDLE_PLUGIN_ROOT` 时,工作区级持久状态锚定在产物根目录 +(`bin/` 的父目录,与生成的 MCP worker 使用同一个回退),而不是 `$PWD/.agent-bundle/state`,这样一起安装 +的 CLI 与服务器观察到的是同一份存储。 + +从其他表面引用它时使用插件根 token——Claude Skill Markdown 与 hook 命令中写 +`${CLAUDE_PLUGIN_ROOT}/bin/.mjs`,Codex hook 中写 `${PLUGIN_ROOT}/…`,Cursor hook 中写 +`${CURSOR_PLUGIN_ROOT}/…`——或者在编译后的脚本里用同级引用 +`new URL('../bin/.mjs', import.meta.url)`。`inspect` 把该 bin 记为每个 target 的一个 `cli` +组件,产物清单会记录这两个文件。不具备 `cli` 能力的 target 会省略该 bin 并报告 `AB4765`;宿主在同一路径 +发射的文件(例如 `claude.bin` 目录中交付的 `.mjs`)是 `AB4766`。包构建的 +`dist/bin/.js` 保持不变。 + +### 把 MCP 工具投影进 CLI + +`routes.mcpCommands` 把生成式 MCP 服务器的工具加入同一张命令图与同一个可执行文件,即使项目完全没有 +`src/cli/**` 路由也可以。`true` 选中每个符合条件的工具;对象形式接受匹配 `:` 身份的 +`include` 与 `exclude` 模式,其中 `*` 是唯一的通配符。 + +每个被投影的工具以 ` ` 运行,协议工具名逐字保留。它唯一的输入选项是 +`--input`,接受一个 JSON 对象。只有当工具的静态 MCP annotations 明确设置了 `readOnlyHint: true` 时 +它才是只读的;其余工具都被视为可变更,并在没有 `--yes` 时失败关闭。每个声明的模式都必须至少匹配一个 +符合条件的工具,拼写错误会以 `AB4822` 失败,并列出可用的身份。 + +## 发布标识 + +插件代码通过框架读取自身标识,而不是维护一个手写的版本模块: + +```ts +import meta, { name, packageName, packageVersion, version } from 'agent-bundle/meta'; +``` + +`version` 是解析出的插件版本,`name` 是宿主原生的插件 slug——绝不是 npm 包名——`packageName` 与 +`packageVersion` 则是经过校验的 npm 轴,对未打包的开发期项目为 `undefined`。编译器会在每个被编译的 +表面替换该 specifier:产物脚本、路由式 CLI、MCP 入口、钩子包装层、包构建,以及浏览器 MCP App 包。 +它是保留 specifier,因此 `tools` 逃生舱无法把它外部化,任何输出包也不可能残留对它的未解析导入。 + +在 agent-bundle 编译之外,已发布的该模块会抛出错误,而不是报告一个伪造的标识;完全没有发布版本号的 +发布构建会被拒绝(`AB4013`)。 + +## 打包与安装器 + +当包输出与至少一个 Claude、Codex 或 Cursor 宿主包在同一项目内构建时,框架还会输出一个自包含、相对 +包路径的安装器,位于 `dist/bin/.js`——若该名称已被占用则为 `-install.js`, +两者都被占用时再追加数字后缀。请在 `package.json` 的 `bin` 中声明对应取值。它的语法是 +`install [--scope ] [--json]`,帮助中只列出已构建的宿主;它通过 `import.meta.url` 而 +不是调用者的工作目录来定位随包发布的产物目录,因此无论当前目录在哪里,从 `node_modules` 中都能正常 +工作。没有任何 npm 生命周期会执行安装。 + +```sh +npx agent-bundle prepack --root . --output artifact --json +``` + +`prepack` 运行发布构建与 `npm pack --dry-run --json --ignore-scripts`,随后对精确的包与产物清单、 +清单哈希、包 bin 目标以及发布版本一致性设卡。把它用作 npm 的 `prepack` 脚本;`--ignore-scripts` +可防止递归。 + +## 预构建 payload + +有些项目确实自行掌控编译——例如逐入口逃生舱无法表达的、协调多环境的打包器拓扑——但仍希望使用框架 +自有的宿主打包。顶层 `payload` 块声明构建将按字节、按稳定路径打包的已构建目录树,而 +`entry: { prebuilt: './dist/…' }` 或 `handler: { prebuilt: './dist/…' }` 则把生成的宿主清单指向这些 +树中的文件,而不编译它们。 + +每个 payload 文件都保留其精确的相对路径,因为框架并没有编译这些文件,也就无法改写它们内部的引用。 +完整性依然是内容寻址的:每个 payload 文件都会带着自己的 SHA-256 与 `prebuilt` 文件类别进入产物清单, +并参与项目 revision 的哈希。请先运行你自己的构建——缺失或为空的 payload 在 `dev` 下只是警告,以便 +干净检出也能工作,但 `agent-bundle build` 会拒绝它。 + +## 打包器逃生舱 + +`tools.rsbuild`(一个 Rsbuild 环境配置片段)与 `tools.rspack`(一个 Rspack 配置对象、mutator 函数或 +数组)会**最后**合并进 agent-bundle 合成的每一份打包器配置:产物脚本、MCP 入口、钩子包装层、MCP App +视图与包构建。这正是消费者永远不需要第二份打包器配置文件的原因。 + +这个逃生舱是有边界的。框架的 invariant 钩子在你的 `tools.rspack` *之后*运行,解析后的配置断言也仍在 +合并之后运行。破坏产物契约的取值——异步 chunk、输出根目录、自包含性——会以硬诊断让构建失败,而不是 +悄悄覆盖契约。保留的模块 specifier 也以同样方式受到保护:把 `agent-bundle/mcp-entry`、 +`agent-bundle/meta` 或 `agent-bundle/mcp-apps` 外部化的逃生舱会让构建失败——对静态可见的 `externals` +在配置检查阶段失败,对函数形式的则通过构建后扫描失败。逃生舱定制的是*代码如何编译*,绝不是*产物承诺 +了什么*。 + +有一个双引擎注意事项:产物脚本、MCP 入口、钩子包装层与包构建通过 Rslib 编译,运行在 `@rslib/core` +内嵌的打包器版本之下;而 MCP App 视图通过工作区固定的 `@rsbuild/core` 编译。因此,从单独安装的 +`@rspack/core` 导入的类,与实际执行配置的那个引擎的类身份并不相同。切勿针对导入的 `@rspack/core` +构造插件或执行 `instanceof` 检查——请改用传给 mutator 函数的 utils 参数: + +```ts +tools: { + rspack: (config, { rspack }) => { + // `rspack` is always the executing engine's own object. + return config; + }, +}, +``` + +想确切看到逃生舱产生了什么: + +```sh +npx agent-bundle inspect --bundler --root . +npx agent-bundle inspect --bundler --root . --target claude --json +``` + +它会把构建所组合的每个输出的合成配置,按构建实际降级的样子原样导出;由于用的就是构建所用的同一批 +函数,这份导出不可能与真实编译结果发生漂移。被框架包装的入口还会附带生成的包装模块源码。 + +本页所有内容的完整契约见 +[Entry conventions](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/entry-conventions.md)。 + +## 下一步 + +- [配置模型](./index.mdx) —— 其余的配置表面。 +- [项目结构](../start/project-structure.mdx) —— 这些入口各自的位置。 diff --git a/website/docs/zh/guide/authoring/scripts-assets.mdx b/website/docs/zh/guide/authoring/scripts-assets.mdx new file mode 100644 index 000000000..66d4d63c1 --- /dev/null +++ b/website/docs/zh/guide/authoring/scripts-assets.mdx @@ -0,0 +1,115 @@ +--- +description: '在每个 agent-bundle 产物中发布可执行脚本与静态资源,包括渲染式 .tsx 脚本与四种 CLI 输出模式。' +--- + +# 脚本与资源 + +脚本是产物中除 MCP 服务器之外携带的可执行文件:Skill 让智能体去运行的那个东西,或者钩子想要执行的 +那项检查。资源则是与它们同行的静态文件。 + +## 脚本 + +顶层 `scripts` 是一个从稳定输出名到入口路径的记录,或映射到一个带 `entry` 和可选 `targets` 限制的 +对象: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { + name: 'hooks-and-scripts', + description: 'Release preparation helpers.', + }, + scripts: { + 'check-service-fixture': './src/scripts/check-service-fixture.ts', + 'detect-risk': { entry: './src/scripts/detect-risk.ts', targets: ['portable'] }, + }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +这里的键是**输出名**,因此即使源文件移动,它也保持稳定。编译方式取决于入口的扩展名: + +| 入口 | 输出 | +| --- | --- | +| JavaScript / TypeScript | 在每个所选 target 产物中打包为 `scripts/.mjs`。 | +| `.sh`、`.bash`、`.py` | 按字节复制,并保留源文件权限位。 | + +### 约定 + +`src/scripts/.ts` 处一个未被认领的普通模块,无需任何声明即可走同一条流水线发布。引用了该文件的 +`scripts` 条目会认领它——上面的例子中 `detect-risk` 之所以保持显式,正是因为它限制了 target。 + +`src/scripts/` 下的嵌套模块是硬错误(`AB4808`)——输出名必须无歧义。给某一段路径加 `_` 前缀,或用 +显式 `scripts` 条目认领该文件,即可退出。 + +每个引用了某模块的显式配置条目——`scripts`、`hooks`、`mcp`、`lib`——都会把它从约定中认领走,只有一个 +例外:`bin` 条目**不会**认领 `src/scripts/.ts` 这样的直接子模块。bin 编译到 `dist/bin/.js`, +与所有 artifact 互不重叠,而且两种外壳运行同一个 `main`,所以该模块会同时作为 npm bin *和* artifact 中的 +`scripts/.mjs` 发布。这样的模块必须导出 `main` 或自执行:只导出 `default` 的普通脚本是 `AB4738`, +渲染式 `.tsx` 脚本则必须同时导出默认组件和 `main`(`AB4737`)。若只想作为 bin 发布,给某一段路径加 `_` +前缀即可。 + +### 渲染式脚本 + +`src/scripts/.tsx` 是渲染式脚本。它的 async 默认组件接收 `argv` 与 `signal`,并按完整的 CLI +输出契约通过 Agent 渲染器渲染。它编译为 `scripts/.mjs`,外加一个 `scripts/-flight.mjs` +react-server worker。 + +扩展名就是显式、可见的契约。普通 `.ts` 脚本绝不会被包进 React 行为里,而显式的 `scripts` 配置条目 +无论扩展名如何都保持普通形态。改名为 `.ts` 即可退出。 + +渲染式脚本与渲染式路由 CLI 命令共享同一套输出契约: + +| 模式 | 行为 | +| --- | --- | +| 交互式 TTY | 进度就地更新;最终文档以 Markdown 打印。 | +| 管道输出 | 恰好一份最终 Markdown 文档,没有任何部分回退。 | +| `--json` | 经过校验的规范最终值。 | +| `--ndjson` | 带序号的渲染事件流。 | + +`--ndjson` 是 agent-bundle 的 CLI 与脚本输出方言,不是 MCP JSON-RPC,并且绝不会作为非 MCP 字节写入 +某个 MCP 服务器的 stdout。诊断信息留在 stderr;机器可读输出独占 stdout。 + +### 运行脚本 + +`script.run` 是生产环境挂载、可信本地的 Workbench Playground 操作。它只在受管工作区中、为所选 target +运行所选的、由清单拥有的输出脚本,并保留有界的 stdout 与 stderr、退出码、取消状态与原始事件引用。 +它无法被交给一条浏览器提供的命令。 + +## 资源 + +根 `assets/` 目录下的文件按字节复制到每个 target 产物的 `assets/` 目录。这个约定不需要任何配置。 + +顶层 `assets` 列表会**取代**该约定,改为显式条目——字面文件路径、整个目录,或 glob,全部相对项目 +根目录解析: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + assets: ['release/*.json', 'evals/fixtures/status/result.json'], + plugin: { + name: 'hooks-and-scripts', + description: 'Release preparation helpers.', + }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +`assets/` 之外的条目在产物的 `assets/` 目录下保留其项目相对路径,因此 `release/notes.json` 会落在 +`assets/release/notes.json`。 + +## 完整性 + +生成的 `agent-bundle.manifest.json` 为每个输出文件记录 SHA-256 摘要,包括被复制的脚本与资源。因此 +产物校验是内容寻址的:它把真实字节与清单比对,而不是检查某个路径是否存在。 + +```sh +npx agent-bundle validate --artifact artifact --strict +``` + +## 下一步 + +- [包入口](./package-entries.mdx) —— `bin`、`lib`、路由式 CLI 与打包器逃生舱。 +- [钩子](./hooks.mdx) —— 经常调用这些脚本的生命周期处理器。 diff --git a/website/docs/zh/guide/authoring/skills.mdx b/website/docs/zh/guide/authoring/skills.mdx new file mode 100644 index 000000000..1d37ef18e --- /dev/null +++ b/website/docs/zh/guide/authoring/skills.mdx @@ -0,0 +1,159 @@ +--- +description: '以带资源的 SKILL.md 文档、宿主扩展、规范路径 token,或渲染式 SKILL.tsx 源码编写 agent-bundle Skill。' +--- + +# Skills + +一个 Skill 就是一份 Markdown 文档,加上它所引用的文件。agent-bundle 从 `src/` 约定中发现 Skill,把 +每个 Skill 降级为每个所选宿主期望的写法,并把该目录中其余文件作为这个 Skill 的资源随行发布。 + +## 约定 + +每个 Skill 一个目录,里面放 `SKILL.md`: + +```text +src/skills/release-review/ +├── SKILL.md +├── assets/report-template.md +└── references/checklist.md +``` + +这样就能随产物发布,无需任何声明。目录中除 `SKILL.md` 之外的所有内容——以及除渲染式 Skill 源文件 +之外,那些是构建输入——都会成为该 Skill 的资源,并保留相对路径复制进产物。 + +## 文档 + +`SKILL.md` 以 YAML frontmatter 开头,其后是普通 Markdown: + +```md +--- +name: release-review +description: Reviews release evidence and issues an auditable readiness verdict. +--- +# Release review + +## When to use + +Use this Skill when a release candidate needs a go/no-go verdict supported by +checked, reproducible evidence. + +## Required resources + +- Read [the release checklist](references/checklist.md) to inspect the artifact. +- Deliver the result with [the report template](assets/report-template.md). +``` + +frontmatter 是必需的。缺少 frontmatter 的文档报告 `AB3001`;YAML 无效则报告 `AB3002`。`name` 与 +`description` 是每个宿主都会读取的字段——description 就是激活表面,因此请把它写成「在什么条件下应当 +使用这个 Skill」,而不是写成标题。 + +指向同级文件的链接是 Skill 引用自身资源的方式。它们在输出文档中保持相对形式,因此在宿主安装的任何 +产物中都能正确解析。 + +## 显式选择 Skill + +约定发现覆盖 `src/skills/*/SKILL.{md,ts,tsx}`。当你需要不同位置、只取子集,或需要一份字面清单时, +再声明 `skills`: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { description: 'Engineering operations.', name: 'ops-bundle' }, + skills: ['src/skills/*'], + targets: ['portable', 'codex', 'claude'], +}); +``` + +字面路径保持字面;glob 匹配 Skill 目录或 `SKILL.md` 文件。配置总是胜过约定。 + +## 宿主扩展 + +可移植的 Skill 元数据——`name`、`description`、`license`、`compatibility`、`allowed-tools`(Agent Skills +的拼写;驼峰式 `allowedTools` 只在 `targets.claude` 下被接受),以及一个 +自由形式的 `metadata` 记录——每个 target 都能理解。宿主专属的键位于各自宿主之下,因此只会抵达该适配器: + +| 宿主 | 示例 | +| --- | --- | +| `claude` | `allowedTools`、`disallowedTools`、`argumentHint`、`model`、`effort`、`context: 'fork'`、`background`、`userInvocable`、`disableModelInvocation`、`whenToUse`、`shell`、`paths`、`hooks` | +| `codex` | `dependencies.tools`、`interface`(显示名、图标、品牌色、默认提示词)、`policy.allowImplicitInvocation` | +| `cursor` | `globs`、`icon`、`color`、`paths`、`disableModelInvocation` | + +Skill frontmatter 中的 `targets` 就是上述按宿主划分的对象,别无他用;写成列表(如 `targets: ['claude']`)是 +`AB3006`。Skill 会输出到项目选择的每一个 target——不存在按 Skill 限制产物的机制。 + +## 路径 token + +各宿主的占位符语法并不相同——`${CLAUDE_PLUGIN_ROOT}` 并不是 Cursor 或可移植标准所使用的写法。请改为 +编写规范 token,由构建期降级替换成宿主写法: + +| Token | 含义 | +| --- | --- | +| `agent-bundle:path:plugin-root` | 插件安装根目录。 | +| `agent-bundle:path:plugin-data` | 宿主提供的插件数据目录。 | +| `agent-bundle:path:workspace-root` | 用户的工作区或项目根目录。 | +| `agent-bundle:token:arguments` | 调用参数。 | +| `agent-bundle:token:session-identity` | 当前会话标识。 | +| `agent-bundle:token:skill-root` | 已安装 Skill 自身的目录。 | + +降级只替换语法;构建期不会解析任何运行时取值。宿主无法表达的 token 会被报告,而不是悄悄以字面文本 +输出。 + +## 渲染式 Skill + +当一份 Skill 文档是生成出来的而不是手写的——例如逐环境重复的同一张检查表,或从某个契约推导出的表格 +——请在 `src/skills//SKILL.tsx`(或 `.ts`)放一个组件。它默认导出一个组件并导出一个 +`frontmatter` 记录;构建会把渲染树编译成宿主消费的 `SKILL.md` 文档: + +```tsx +// src/skills/release-review/SKILL.tsx +import { Skill } from 'agent-bundle'; + +export const frontmatter = { + name: 'release-review', + description: 'Reviews release evidence and issues an auditable readiness verdict.', +}; + +export default () => ( + <> +

Release review

+

Evidence lives under {Skill.PluginRoot()}/assets.

+ +); +``` + +六个 `Skill.*` 成员会输出规范 token:`Skill.Arguments`、`Skill.PluginData`、`Skill.PluginRoot`、 +`Skill.ProjectRoot`、`Skill.SessionIdentity` 与 `Skill.SkillRoot`。`Skill.Resource` 渲染的是 +Markdown 链接(`[path](path)`),不是 token。宿主语法在降级阶段应用,绝不在组件里应用。 + +同目录下手写的 `SKILL.md` 总是胜出——手写文件胜过生成文件——被遮蔽的组件会报告信息级的 `AB4735` +提示。渲染模块加载失败报告 `AB3003`;未默认导出组件函数、或未导出 `frontmatter` 记录,则报告 +`AB3004`。 + +`defineSkill` 可以在渲染式源码旁给 Skill 定义加上类型: + +```ts twoslash +import { defineSkill } from 'agent-bundle'; + +export const skill = defineSkill({ + description: 'Reviews release evidence and issues an auditable readiness verdict.', + name: 'release-review', +}); +``` + +## 检查发布了什么 + +```sh +npx agent-bundle inspect --root . --skills +``` + +skill focus 会展示每个被发现的 Skill、它的 provenance(`conventional` 或 `config`)、它的资源,以及 +逐 target 的降级判断。在开发者 Workbench 中,Skills 页面会渲染每个宿主的输出文档。 + +Skill Markdown 中的原始 HTML、JSX/MDX 与 Mermaid 在 Workbench 渲染器中是惰性的。这是刻意的隔离边界, +不是渲染缺失。 + +## 下一步 + +- [钩子](./hooks.mdx) —— 对生命周期事件作出反应。 +- [MCP 服务器与 MCP App](./mcp.mdx) —— 暴露工具、资源与提示词。 diff --git a/website/docs/zh/guide/development/_meta.json b/website/docs/zh/guide/development/_meta.json new file mode 100644 index 000000000..019782edf --- /dev/null +++ b/website/docs/zh/guide/development/_meta.json @@ -0,0 +1 @@ +["index", "workbench", "testing", "evaluations"] diff --git a/website/docs/zh/guide/development/evaluations.mdx b/website/docs/zh/guide/development/evaluations.mdx new file mode 100644 index 000000000..d8e228f15 --- /dev/null +++ b/website/docs/zh/guide/development/evaluations.mdx @@ -0,0 +1,138 @@ +--- +description: '运行 agent-bundle eval 套件:pass/fail/inconclusive 语义、最低证据、smoke 与可靠性之别、对比对齐,以及需要认证的原生 harness。' +--- + +# 评测 + +测试证明路由能渲染、生成式服务器遵守线上契约。Eval 追问的是另一个问题:给定一段提示词,智能体真的 +抵达了你的插件吗?其结果是否就是你所声称的那个?这个答案是概率性的,因此 eval 运行器的设计目标是诚实 +报告,而不是看起来一片绿。 + +## 套件是带类型的模块 + +Eval 套件按约定从 `evals/**/*.eval.ts` 中发现,每个套件默认导出 `defineEvalSuite`: + +```ts twoslash +import { defineEvalSuite, expectOutcome, expectSkillActivation } from 'agent-bundle/eval'; + +export default defineEvalSuite({ + cases: [{ + assertions: [ + expectSkillActivation({ skill: 'service-readiness' }), + expectOutcome({ script: './graders/status-result.ts' }), + ], + fixture: './fixtures/status', + hosts: { portable: { model: 'deterministic' } }, + id: 'status-is-healthy', + invocation: { mode: 'explicit', skill: 'service-readiness' }, + prompt: 'Use service-readiness to verify the checked-in compiler service fixture.', + trials: 1, + }], + name: 'mcp-app-status', +}); +``` + +一个 case 声明它的 `id`、`prompt`、`fixture`、`hosts`(逐宿主固定模型)、`invocation` 模式—— +`automatic`、`explicit` 或 `none`——它的 `assertions`,以及要运行多少次 `trials`。同一套件内的 case id +必须唯一。套件发现、运行记录目录与可选的语义 grader 都配置在顶层 `evals` 配置块中;见 +[配置](../../reference/configuration.mdx)。 + +```sh +npx agent-bundle eval --root . --suite mcp-app-status --trials 3 +npx agent-bundle eval compare +``` + +## 三种结果,而不是两种 + +每个断言都解析为 `pass`、`fail` 或 **`inconclusive`**,并声明它所接受的最低证据。当断言所需的证据强于 +harness 实际产生的证据时,它是 inconclusive ——绝不会被悄悄判为通过。 + +| 证据 | 含义 | +| --- | --- | +| `observed` | harness 在宿主自己的输出中看到了这件事发生。 | +| `inferred` | harness 由周边证据推导得出,而不是直接观察到。 | +| `unavailable` | 完全没有证据。它绝不是合法的声明最低值,因为那等于接受证据缺失。 | + +断言辅助函数会把 `minimumEvidence` 默认设为它们的证据所能支撑的最强门槛:`expectExitCode`、 +`expectMcpCall`、`expectNoMcpCall` 与 `expectOutcome` 默认 `observed`,而 `expectSkillActivation` 与 +`expectNoSkillActivation` 默认 `inferred`,因为并非每个宿主都会公布激活事件。当你希望较弱的证据读作 +inconclusive 而不是通过时,用 `minimumEvidence: 'observed'` 抬高默认值。 + +由于 inconclusive 的试验没有产生任何证据,它同样无法报告成功:只要一次运行中存在失败**或** +inconclusive 的试验,`agent-bundle eval` 就以非零退出。 + +## smoke 证据与可靠性数字 + +少于**三**次试验会被报告为 smoke 证据,而不是一个可靠性数字。一次通过的试验说明这条路径存在,但它不是 +一个比率。 + +只有当以下每个 facet 都匹配时,对比行才算对齐:case、fixture、语义 grader 身份、harness、宿主 CLI +版本、invocation 与 model。产物 target 摘要刻意*不是*对齐 facet ——它正是被对比的那个对象。运行中未 +记录的 facet 会被标注为未核实(unverified),而不是被假定为对齐;某个 facet 不匹配会让该行变为 +non-comparable,而不是产出一个没人该信的差值。 + +## 可选的语义 grader + +语义 grader 用恰好一个被固定的 Claude 模型来配置: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + evals: { + semanticGrader: { harness: 'claude', model: 'claude-sonnet-4-5' }, + }, + plugin: { description: 'Evidence-backed project tools.', name: 'my-plugin' }, + targets: ['portable', 'claude'], +}); +``` + +它只在 `agent-bundle eval --harness claude` 下运行,且只针对固定到 Claude 的 case。在主轨迹可用且确定性 +grader 完成之后,Agent Bundle 会发起**一次**服务端自有、不含插件的 Claude 评分调用。它的固定结果 id 是 +`claude-semantic`,其请求、原始流、stderr 与规范 provenance 会与试验产物一并保留。格式错误或失败的语义 +grader 会让该次试验保持 inconclusive,而不是变成插件证据——坏掉的 grader 不等于插件行为异常。 + +语义评分需要原生 Claude harness 与一个已登录的 Claude Code 会话。配置了它之后,确定性与 Codex 选择会 +被拒绝。 + +## Eval 页面 + +Workbench 的 Eval 页面会受理一次选定的运行、报告实时进度,并可通过运行生命周期取消它。每次试验都会在 +存在时暴露其持久化的原始证据,以及记录下来的 CLI、invocation、grader 与用量 provenance。 + +对比单元格会展示记录下来的 provenance 与用量,并且只纳入已对齐的 case、fixture、harness、invocation、 +宿主与 model、CLI 以及 grader facet;未匹配的 facet 会被标注为 non-comparable 或未核实。试验时长会被 +持久化;供应商 token 用量只在原生流上报了它时才展示。 + +Playground 可以把一条轨迹中选定的持久 outcome 与断言证据提升为一份草稿 eval case ——见 +[开发者 Workbench](./workbench.mdx)。 + +## 原生 harness + +确定性 harness 不需要安装任何东西。原生 Claude 与 Codex harness 运行真实的宿主 CLI,而那里的规则很严格: + +- 本包绝不接受、索取、注入或持久化任何模型供应商 API key。 +- 原生 harness 使用**已安装且已登录**的 CLI,并且是在移除供应商 key 环境变量之后运行。 +- 缺失、不兼容或未认证的 CLI 会被报告为 *harness* 失败,与插件失败明确区分。 +- 原生 smoke 是选择加入的,不属于普通测试运行的一部分。 +- Codex 不公布权威的 Skill 激活事件,因此 Codex 的激活证据是 `inferred`,绝不会被报告为 `observed`。 + +若要做一次手动的、已认证的本地 smoke,请先完成每个 CLI 各自的常规交互式登录,然后核实 CLI 是受支持的 +非预发布版本。Claude Code 至少需要 `2.1.232`;Codex 至少需要 `0.147.0`: + +```sh +claude --version +npx agent-bundle eval --harness claude --trials 1 + +codex --version +npx agent-bundle eval --harness codex --trials 1 +``` + +每次 Codex 试验都会设置一个临时的 `CODEX_HOME`,并只把已安装 CLI 的不透明 `auth.json` 复制进去。你 +常用的 Codex home、配置与已安装插件状态不会被用作试验状态,也不会被改动。完整的凭据契约见 +[安全](../../reference/security.mdx)。 + +## 下一步 + +- [分发](../distribution/index.mdx) —— 把一份已校验的产物变成可安装的捆绑包。 +- [已知限制](../../reference/limitations.mdx) —— eval 与 harness 目前不能证明什么。 diff --git a/website/docs/zh/guide/development/index.mdx b/website/docs/zh/guide/development/index.mdx new file mode 100644 index 000000000..3d06c16da --- /dev/null +++ b/website/docs/zh/guide/development/index.mdx @@ -0,0 +1,51 @@ +--- +description: 'agent-bundle 的开发循环:开发服务器、重建 epoch,以及把一次构建变成证据的三个表面。' +--- + +# 开发循环 + +开发只有一个入口,以及三个证据表面。入口是 `agent-bundle dev` —— 一个前台、仅绑定 loopback 的 +服务器,它随输入变化重建产物。三个表面分别是开发者 Workbench、测试 harness 与 eval 运行器。 + +```sh +npx agent-bundle dev --root . +``` + +## 重建循环 + +`dev` 会准备项目源码、构建产物,并把它发布为一个 **epoch**:由 `epochId` 标识的不可变世代。任何读取 +已构建产物的表面——Workbench 产物树、一个 MCP 会话、一次钩子模拟、一个开发期宿主安装——都会指明自己 +读取的是哪个 epoch,因此会话进行中的一次重建绝不会悄悄改变某个结果所证明的对象。 + +重建是防抖且串行的。构建成功会发布一个 `artifact.available` 事件;构建**失败**则什么都不发布,因此 +先前发布的 epoch 保持原样。不存在只发布了一半的世代。 + +当项目声明了 `bin`/`lib` 条目时,同一个串行流程也会重建 `dist/` 包构建。该构建有自己基于 provenance +的增量边界:一次成功之后,每个输出文件的排序后源输入会被保留下来,除非以下情况之一发生,下一次重建 +就会被跳过:某个被记录的输入失效、配置文件或 `package.json`、`tsconfig.json` 发生变化、规范化后的 +`bin`/`lib` 声明或 `tools` 逃生舱发生变化、这次失效是手动或初始触发的,或者上一次包构建失败了。 +一个新增文件如果改变了模块解析却没有触及任何被记录的输入,会在下一次被记录的变化时才被捡起,而不是 +立即生效。 + +包构建失败绝不会让已经提交的产物 epoch 失效。它会作为一条 `AB7103` **警告**出现在那次成功的构建 +尝试上,并在下一次失效时重试。 + +开发期还会从同一份编译后的路由图,把生成的路由声明发布到 `.agent-bundle/routes.d.ts`。每次写入都先 +写入一个同级临时文件,再原子地重命名覆盖先前那份完整声明,因此无效源码会保留上一份可用文件,而一次 +成功的、不含路由的准备过程会移除它。 + +## 三个表面 + +| 表面 | 它是什么 | 它证明什么 | +| --- | --- | --- | +| [开发者 Workbench](./workbench.mdx) | `dev` 提供的 loopback 界面:诊断、Skill、产物 provenance、MCP 与钩子 playground、Playground 轨迹、eval 运行。 | 它自身不证明任何东西——它是你*查看*并*演练*真实生成输出的地方。 | +| [测试](./testing.mdx) | `agent-bundle/rstest` 与 `agent-bundle/test`,以及框架自有的契约矩阵。 | 可复核的证明级别,从路由模块渲染,一直到从已安装宿主布局中启动的捆绑包。 | +| [评测](./evaluations.mdx) | 通过确定性、Claude 或 Codex harness 运行的带类型 eval 套件。 | 智能体是否真的抵达了你的插件,并以 `pass` / `fail` / `inconclusive` 与声明的最低证据门槛报告。 | + +能构建的插件不等于能工作的插件,而以上三者回答的是不同的问题。它们互不替代。 + +## 本章的边界 + +编写表面——Skill、钩子、MCP 路由、脚本或包入口*是什么*——在[编写](../authoring/index.mdx)中。把一份 +已校验的产物变成宿主能安装的东西,在[分发](../distribution/index.mdx)中。精确的命令行标志、配置字段 +语义与运行时契约在[参考](../../reference/index.mdx)中。 diff --git a/website/docs/zh/guide/development/testing.mdx b/website/docs/zh/guide/development/testing.mdx new file mode 100644 index 000000000..e5c6eea09 --- /dev/null +++ b/website/docs/zh/guide/development/testing.mdx @@ -0,0 +1,207 @@ +--- +description: '通过框架 harness 测试 agent-bundle 路由:rstest 配置辅助函数、renderRoute、九个证明级别,以及生成式插件的契约矩阵。' +--- + +# 测试 + +路由模块通过框架来测试,而不是通过手写的打包器配置。承载该 harness 的有两个子路径,且两者都是选择 +加入的:`@rstest/core` 与 `react` 是可选 peer 依赖,因此从不测试路由的项目两者都不会安装。渲染还需要 +`@agent-bundle/runtime`,只要项目拥有路由模块就已经拥有它——生成的入口以同样的方式导入它。 + +## 配置辅助函数 + +`agent-bundle/rstest` 会编译项目一次——与构建执行的路由图编译完全相同,但不构建产物——并返回一个普通的 +Rstest 配置对象,其中携带测试清单、路由加载器、React 的 `react-server` 解析条件以及自动 JSX 运行时: + +```ts +// rstest.route-unit.config.ts +import { defineConfig } from '@rstest/core'; +import { agentBundleRstest } from 'agent-bundle/rstest'; + +export default defineConfig(await agentBundleRstest()); +``` + +route-unit 测试默认匹配 `tests/route-unit/**/*.test.{ts,tsx}`,并且需要**自己独立的** Rstest 运行, +因为渲染一个路由要求整个 worker 进程启用 Node 的 `react-server` 条件。请把它们排除在项目普通的 +`rstest` 运行之外。 + +## 渲染一个路由 + +`agent-bundle/test` 提供这些辅助函数。`renderRoute` 执行一个路由——通过编译后的 route id,或直接导入 +模块——它经过真实的渲染器与真实的请求存储,并解析为最终的 Agent Document: + +```ts twoslash +import { expectDocument, renderRoute } from 'agent-bundle/test'; + +export const summarizes = async (): Promise => { + const { document } = await renderRoute('tool:library/summarize', { + input: { title: 'Dune' }, + }); + + expectDocument(document) + .toHaveStatus('success') + .toContainMarkdown('Dune') + .toHaveValue({ chapters: 24 }); +}; +``` + +`renderRoute` 接受 `input`、`args`(CLI 路由)、请求 `context` 覆盖(包括一个 `context.progress` +上报器)、渲染 `limits` 以及一个 `signal`。它返回文档、路由上报的请求作用域进度、解析出的 provenance, +以及由路由自己的 `resultSchema` 解析后的取值。无论调用方是否提供自己的上报器,进度都会被记录。 + +`testManifest()` 暴露编译后的路由清单,因此一个测试套件可以在进程内遍历每个路由,而不必为每个路由付出 +一次构建的代价。任何失败——未知路由、被拒绝的路由种类、被拒的输入、渲染错误——都会指明 route id、target +种类与模块 provenance。 + +针对 Agent Document 契约的匹配器:`toHaveStatus`、`toContainMarkdown`、`toContainText`、 +`toHaveValue`、`toHaveError` 与 `toHaveNodeKinds`。 + +这就是 route-unit 证明级别,且仅此而已:它证明一个路由模块渲染出了它所声称的文档。它不是关于 MCP +传输、打包产物或浏览器表面的证据。 + +## 证明级别 + +这些级别是刻意分开的。每个辅助函数都会把自己承载的级别写进 provenance,并在每次失败时打印它,因为 +某一级别的通过绝不是另一级别的凭据。 + +| 级别 | 辅助函数 | 它证明什么 | +| --- | --- | --- | +| `route-unit` | `renderRoute`、`renderRouteEvents` | 一个路由模块渲染出了它所声称的文档——以及渲染事件流。 | +| `mcp-in-memory` | `openInMemoryMcpServer`、`invokeMcpTool`、`readMcpResource`、`getMcpPrompt`、`listMcpSurface`、`runContractMatrix` | 真实生成式 MCP 服务器的协议契约,经由 SDK 的内存内传输。 | +| `dev-epoch` | `runDevEpochContractMatrix` | 通过 Workbench 会话服务打开的、锁定到某个 epoch 的生成式 stdio 进程;调用方拥有 epoch 租约与进程生命周期,MCP App 路由被覆盖(表面加 `ui://` 扫描)。 | +| `cli-dispatch` | `invokeCli`、`cliJson`、`cliNdjson` | 一个普通或渲染式 argv 向量在路由式 CLI 自己的 shell 中被解析并执行——包括渲染式 Markdown、显式 TTY、JSON 与 NDJSON 模式——全部在进程内完成。 | +| `packed-stdio` | `openPackedMcpServer`、`runPackedContractMatrix` | 已构建产物的生成入口作为真实进程通过 stdio 运行。 | +| `packed-deleted-source` | `removeProjectSource`、`openPackedMcpServer({ deletedSource })`、`runPackedContractMatrix` | 在项目源码与配置被移除并核实缺失之后,打包后的 stdio 进程仍然可以运行。 | +| `host-install` | `openInstalledHostMcpServer`、`runInstalledHostContractMatrix` | 一份已构建的捆绑包被投放到隔离的宿主根目录、以输出的宿主格式被发现,并从已安装布局中启动。 | + +在这七个级别之外还有两个并列级别,共九个。`agent-bundle/test/browser` 为浏览器安全的 `browser-app` 级别 +提供 `mountBrowserApp`,用于在真实浏览器页面中把生产编译的 MCP App HTML 挂载到产品桥接层之上; +而 `simulated` 复用不带 `sessionEvidence` 的已安装宿主辅助函数 `openInstalledHostMcpServer` +—— 一份输出的捆绑包被直接投放到隔离的宿主形状根目录并在没有宿主自有安装的情况下启动,它比 +`host-install` 更弱。 + +```ts twoslash +import { cliJson, cliNdjson, invokeCli, invokeMcpTool } from 'agent-bundle/test'; + +export const proofs = async (): Promise> => { + // mcp-in-memory: the generated server projects the document to protocol content. + const call = await invokeMcpTool('summarize', { input: { title: 'Dune' } }); + + // cli-dispatch, plain .ts route: resolve argv, execute, and map the exit code. + const run = await invokeCli(['library', 'audit', './books', '--max-files', '8']); + + // cli-dispatch, rendered .tsx route: exercise the shell's rendered output modes. + const rendered = await invokeCli(['library', 'report', './books', '--ndjson']); + const events = cliNdjson(rendered); + + // An explicit TTY proves the in-place progress path rather than the piped one. + const tty = await invokeCli(['library', 'report', './books'], { tty: true }); + + return { + exitCode: run.exitCode, + finalEvent: events.at(-1)?.type, + inPlaceProgress: tty.stdout.includes('\r\u001B[2K'), + scanned: cliJson(run), + structured: call.structuredContent, + }; +}; +``` + +`expectEvents` 针对渲染事件流做断言。`toContainSequence` 对序列是宽容的——多出一个 `progress` 或 +`replace` 帧是合法的,不会把一次本该通过的渲染判红——而缺帧、乱序或序号回退仍然会失败。 +`toHaveMonotonicSequence`、`toCompleteOnce`、`toHaveProgress` 与 `toHaveNoErrors` 覆盖契约的其余部分。 + +### 进程证据刻意很昂贵 + +在打包级别中,只有 `packed-stdio` 及其严格更强的升级版 `packed-deleted-source` 属于进程证据:打包一次、 +安装一次、构建一次、移除并核实源码缺失一次、启动一次,然后在这一个会话内遍历每个路由的断言。`dev-epoch` +是另一种形态的进程证据——Workbench 自己的、锁定到 epoch 的生成式 stdio 进程,而非打包后的产物——因此 +`dev-epoch` 通过并不能说明打包会交付什么。deleted-source 的 +旅程还会从生成式服务器读取内嵌的 MCP App 资源;它不证明原生宿主的安装或派发,也不证明那种把产物复制 +到别处的安装方式。 + +`host-install` 是独立的、关于已安装布局的进程证据。它的确定性适配器模拟通道是无条件运行的,可用的 +Claude 与 Codex 二进制文件还会证明它们各自公开的安装路径,而 Cursor 会显式记录它那不可用的、非交互式 +宿主会话表面。 + +## 契约矩阵 + +契约矩阵是框架自有的生成式插件线上契约测试套件。三个入口共享同一份实现;边界差异是显式的能力标记,而 +不是分叉的检查逻辑。项目只需提供 fixture ——合法输入、为每个内存内工具路由声明的 `resultCompat` 策略、 +可选的 `previousResults` 负载、可选的 `cancellation` case,以及一个可选的、带声明式预期的确定性生命 +周期状态转换驱动。 + +**`runContractMatrix`(`mcp-in-memory`)** 通过 SDK 的内存内传输,对真实的生成式服务器打开一个真实的 +MCP 客户端并运行完整矩阵。它证明:线上表面相对编译器清单的完整性、fixture 覆盖率、成功路径的调用扫描、 +经由每个工具路由自己的 `resultSchema` 的 JSON 序列化往返、在序列化负载上声明的 additive 或 closed +兼容行为、当前 schema 对旧版服务器负载的接受度、对由所公布的 `listTools` 输入 JSON Schema 派生出的 +负面输入的拒绝,以及调用中途取消的卫生性。内存内传输可能在不做序列化的情况下传递结构化取值,因此矩阵 +会在校验之前用一次显式的 `JSON.parse(JSON.stringify(...))` 往返来填上这个缺口。MCP App 在表面注册上 +被报告为 not-applicable,因为内存内级别并不注册它们。 + +**`runPackedContractMatrix`(`packed-stdio` / `packed-deleted-source`)** 针对一个已经打开的打包会话 +运行——那一次打包旅程自己拥有会话的打开与关闭。它为以下内容提供进程 stdio 证据:表面完整性(包括 +`listResources` 中编译后的 MCP App 资源 URI)、fixture 覆盖率、成功路径扫描、对所公布输入 schema 的 +拒绝,以及客户端侧的取消卫生性。它无法加载项目路由模块,因为源码可能已被删除并核实缺失,所以序列化 +往返、兼容性探测与版本偏移检查——包括它们逐生命周期阶段的变体——都会以诚实的理由被报告为 +`not-applicable`。打包后的服务器在返回之前会用自己内置的 `resultSchema` 校验每个工具结果;一次成功的 +扫描调用就是那份证据。 + +**`runInstalledHostContractMatrix`(`host-install`)** 针对 `openInstalledHostMcpServer` 返回的、已经 +打开的会话运行。打开器会从已安装的根目录读取宿主输出的 MCP 文档,核实清单、组件路径、资源路径与钩子 +路径以及产物文件摘要,启动那条已安装的命令,并从真实的 MCP `initialize` 结果中观察运行中的版本。它的 +报告分别记录源码、已构建产物、已安装产物与运行中进程的版本,任何取值缺失或不一致都会失败关闭。元数据 +记录被观察到的宿主二进制版本、适配器修订号、清单与 schema 摘要,以及框架版本。以模块为后端的检查仍然 +诚实地是 not-applicable,因为加载项目模块会跨回源码与构建树。 + +```ts twoslash +import { runContractMatrix } from 'agent-bundle/test'; + +export const matrix = async (): Promise => { + await runContractMatrix({ + fixtures: { + 'tool:library/summarize': { + input: { title: 'Dune' }, + previousResults: [{ chapters: 24 }], + resultCompat: 'additive', + }, + }, + }); +}; +``` + +打包与已安装宿主这两个入口接受同样的 fixture 形状,外加它们所针对的会话:`runPackedContractMatrix` +需要那个已打开的打包会话,以及在移除源码*之前*编译出的清单;而 `runInstalledHostContractMatrix` 需要 +`openInstalledHostMcpServer` 返回的会话以及同一份清单。 + +### 生命周期 fixture + +生命周期 fixture 会在矩阵那一个已打开的客户端上重放 +`unknown → queued → running → first-progress → repeated-progress → terminal`。框架会校验每个阶段的 +结构化内容与渲染输出、additive 与 closed 兼容性、结算之前的实时进度、日志累积、声明的通知、幂等的 +提交重放,以及带类型的预算拒绝。 + +调用方提供的同存储 `restart` 回调会在该边界上补充持久性证据;没有它时,这项检查诚实地是 +`not-applicable`。打包侧的调用方应把该回调接到既有打包旅程的重启上,而不是另建一条打包、构建与安装的 +路径。生命周期 fixture 可选的 `state.catalog` 断言会把它声明的 id 与 lifetime 固定到同一次挂载状态 +重放所使用的编译器清单上。 + +### 事件路由与运行时身份 + +当编译后的清单包含事件路由时,打包与已安装宿主这两个边界会在顺序矩阵事件之前以及整个过程中,采样只读的 +事件运行时状态。如果预热的 `instanceId` 发生变化、产物 epoch 漂移,或者可用性降级为 +`runtime-restarted` 或 `runtime-unavailable`,`runtime-instance-identity` 检查就会失败。内存内运行 +以及不含事件路由的编译产物,会诚实地把运行时身份报告为 `not-applicable`。 + +没有任何矩阵边界证明浏览器 App HTML 或产物重建重放。 + +当所公布的输入 schema 声明了 `additionalProperties: false` 时,普通的 `z.object` 工具路由仍可能在不 +触发协议失败的情况下剥掉未知键。当其他生成的负面输入仍然证明了拒绝路径时,负面输入检查会记录这种容忍。 + +失败的矩阵会抛出一个聚合后的 `AgentTestError`,错误码为 `contract-violation`,其中指明每个失败的路由、 +每项失败的检查,以及这次运行实际承载的证明级别标签。 + +## 下一步 + +- [评测](./evaluations.mdx) —— 追问智能体是否根本抵达了插件的那个表面。 +- [产物校验](../distribution/validation.mdx) —— 为已构建捆绑包把关的那些检查。 diff --git a/website/docs/zh/guide/development/workbench.mdx b/website/docs/zh/guide/development/workbench.mdx new file mode 100644 index 000000000..86988d157 --- /dev/null +++ b/website/docs/zh/guide/development/workbench.mdx @@ -0,0 +1,164 @@ +--- +description: 'agent-bundle 开发者 Workbench:loopback 边界、按 epoch 固定的 MCP 会话、Playground 轨迹、开发期宿主安装,以及可选的 Agent API。' +--- + +# 开发者 Workbench + +`agent-bundle dev` 通过 loopback 提供一个预构建的开发者 Workbench。它是你查看编译器实际输出了什么 +(逐宿主、逐 epoch),以及运行输出的包装层的地方,而不是仅凭构建通过就相信插件可用。 + +```sh +npx agent-bundle dev --root . +npx agent-bundle dev --root . --port 3100 --no-open +``` + +## 边界 + +这些是契约,不是默认值: + +- **仅 loopback。** 服务器只绑定 loopback,绝不会暴露到本机之外。 +- **前台开发会话,而不是托管服务。** 关闭进程即结束会话。 +- **按 epoch 固定的读取。** 没有任何东西会隐式跟随一次新构建;读取产物的表面会指明自己读取的 epoch。 +- **只做可信本地操作。** 浏览器绝不提供命令、工作目录、原生模型或凭据。见[安全](../../reference/security.mdx)。 + +## 它展示什么 + +| 页面 | 内容 | +| --- | --- | +| Overview | 项目标识、规范化模型与诊断。 | +| Skills | 每个 Skill 文档,包括各宿主降级后的输出。 | +| Artifacts | 带 provenance 与 epoch 对比的产物树。 | +| MCP | 绑定到产物的 playground,带原始协议轨迹。 | +| Hooks | 运行输出的钩子包装层的 playground。 | +| Playground | 可重放、可导出的持久有序轨迹。 | +| Evals | eval 运行与运行对比。 | +| Logs | 按生产者分组的精简事件与原始 stdout、stderr、协议流:规范化、构建、诊断、MCP、钩子、宿主试验与 grader。 | + +## MCP 会话绑定到一个 epoch + +Workbench 的 MCP 会话在打开时绑定 `{ epochId, target, serverName }`,并且绝不会自动迁移到新的 +epoch。这正是协议轨迹有意义的原因:其中每一帧都来自同一个由同一批输入构建出的生成式服务器。 + +- **Restart MCP session** 会在它*所选*的那个 epoch 上重新启动该生成式服务器。 +- 要使用新发布的 epoch,请打开一个**新**会话。 +- 兼容的 MCP App 通过同一个已绑定会话预览。 + +## Playground 拥有自己的轨迹 + +只有在 Playground 中发起的操作才会加入它的持久有序轨迹。即使 Playground 会话处于打开状态,Hooks 与 +MCP 页面的操作也保持独立——轨迹记录的是一段刻意为之的序列,而不是所有被点过的东西。 + +从一条 Playground 轨迹出发,你可以重放或导出原始证据,或者把选定的持久 outcome 与断言证据提升为一份 +草稿 eval case。 + +`script.run` 是生产环境挂载、可信本地的 Playground 操作:它只在受管工作区中、为所选 target 运行选定的、 +由清单拥有的输出脚本,并保留有界的 stdout 与 stderr、退出码、取消状态与原始事件引用。见 +[脚本与资源](../authoring/scripts-assets.mdx)。 + +原生提示词为所选 epoch 选择一份服务器目录选择——case、fixture、宿主与固定模型——而不是接受浏览器提供的 +命令或模型。 + +## 以编程方式使用同一个会话 + +公开的 `startDevServer` 导出接受 CLI 标志所映射的那些选项(`root`、`port`、`open`、`agentApi`、 +`installHosts`),并解析为一个 `DevServerSession`,它暴露 loopback 的 `url`、一个 `status()` 快照 +以及 `close()`: + +```ts twoslash +import { startDevServer } from 'agent-bundle'; + +export const serve = async (): Promise => { + const session = await startDevServer({ port: 3100, root: process.cwd() }); + console.log(session.url); + await session.close(); +}; +``` + +## 开发期宿主安装 + +多次传入 `--install-host `,即可把开发变体安装到所选宿主: + +```sh +npx agent-bundle dev --install-host cursor --install-host claude +``` + +第一个成功的 epoch 使用普通的宿主安装器,因此 Claude 与 Codex 会正常注册插件,并从宿主自有的 +`plugins/cache///` 目录读取它的文件;Cursor 读取 +`~/.cursor/plugins/local/`。被安装的根目录带有一份 `.agent-bundle-dev.json`,其中记录 schema +版本 `1`、项目根目录、宿主以及已安装的 epoch。 + +它的 MCP 文档始终通过运行中的开发服务器的 Node 可执行文件启动框架 CLI: + +```sh +agent-bundle dev proxy --root --server --target +``` + +重建绝不会用某个 epoch 路径替换这条稳定命令,而宿主进程 `PATH` 的内容也不影响项目本地的框架能否被 +启动。 + +之后每个 `artifact.available` 事件都会把新的 target 复制成一个不可变的已安装世代。顶层目录通过原子的 +符号链接(或 Windows junction)重命名切换,顶层文件通过原子的同级文件重命名切换,因此宿主看到的要么是 +旧的、要么是新的完整条目,任何被同步的目录都不会在两个世代之间消失。发布失败会把指针回滚到先前世代, +并在 `dev.host.sync` 上发出一条 `AB7202` 诊断;构建失败则根本不会发出 `artifact.available`,因此 +最后一次可用的安装不受影响。重新同步会直接写入宿主缓存,不会再次调用 Claude 或 Codex CLI。 + +停止开发服务器会把标记为开发用的安装留在原处。钩子与 Skill 仍留在磁盘上,而那条稳定的 proxy 命令会 +失败关闭(fail closed),直到该项目的开发服务器再次运行。 + +## 实时宿主 MCP 代理 + +在 `dev` 于其背后重建生成式服务器的同时,宿主可以让一个 stdio MCP 进程保持连接。该命令、 +`/mcp/host/` 端点,以及它如何通过项目的开发锁完成发现,都记录在 +[MCP 服务器与 MCP App](../authoring/mcp.mdx)中。 + +这里有一条重建规则值得注意:崩溃的生成式服务器**不会**在同一个 epoch 内被静默重启。在一次成功的重建 +换入一个新预热的 epoch 会话之前,调用会一直保持失败。 + +## 可选的 Agent API + +Agent API 是面向 Codex 客户端的、独立且需要认证的 Streamable HTTP MCP 端点。它**默认关闭**,并且只 +挂载在既有 loopback 前台服务器的 `/mcp` 上: + +```sh +AGENT_BUNDLE_AGENT_API_TOKEN='replace-with-a-secret' \ + npx agent-bundle dev --agent-api --no-open --port 3100 +``` + +`dev: { agentApi: true }` 可从配置启用它,而 `--no-agent-api` 会覆盖该设置。如果端点被启用却没有 +`AGENT_BUNDLE_AGENT_API_TOKEN`,启动会在**开始服务之前**失败。这个固定 token 只被读取一次,绝不会被 +记录日志、持久化或返回,并且必须以标准的 `Authorization: Bearer` 认证方式提供。客户端可以省略 +`Origin`;一旦提供了 origin,它必须与前台 URL 完全一致。端点被禁用时是不存在的,而不只是未授权。 + +它恰好有十三个固定且有序的工具: + +| # | 工具 | # | 工具 | +| --- | --- | --- | --- | +| 1 | `project_status` | 8 | `hooks_list` | +| 2 | `skills_list` | 9 | `hook_simulate` | +| 3 | `skill_inspect` | 10 | `evals_list` | +| 4 | `artifacts_list` | 11 | `eval_run` | +| 5 | `artifact_inspect` | 12 | `eval_get` | +| 6 | `mcp_servers_list` | 13 | `diagnostics_list` | +| 7 | `mcp_invoke` | | | + +`eval_run` 仅限确定性 harness;它无法选择原生宿主。工具 schema 会拒绝未声明的 root、path、command、 +cwd、environment、harness、evidence 与 outcome 字段。以产物为后端的调用可以指名一个 epoch id;否则 +它们会原子地租借当前活跃的 epoch,因此一次热重建会把之后的调用送往新的 epoch,而已被受理的调用仍固定 +在它原本的 epoch 上。传输是无状态的,因此前台服务器恢复之后,已完成初始化的客户端可以在同一个固定 URL +上继续发起请求。 + +## 贡献者 UI 的 HMR + +开发 Workbench 界面本身,与消费一个已发布的 Workbench 是两个不同的循环。只在前台服务器运行时启动它: + +```sh +AGENT_BUNDLE_WORKBENCH_API_PROXY=http://127.0.0.1:3100 pnpm --filter agent-bundle-workbench dev +``` + +`packages/workbench/scripts/dev.mjs` 要求提供该代理 URL。已发布的 `agent-bundle dev` 提供的是预构建 +资源与项目事件;它不会运行 Rsbuild 开发服务器。 + +## 下一步 + +- [测试](./testing.mdx) —— Workbench 所演练表面背后的证明级别。 +- [评测](./evaluations.mdx) —— eval 运行、对比与 Eval 页面。 diff --git a/website/docs/zh/guide/distribution/_meta.json b/website/docs/zh/guide/distribution/_meta.json new file mode 100644 index 000000000..e2a750e4e --- /dev/null +++ b/website/docs/zh/guide/distribution/_meta.json @@ -0,0 +1 @@ +["index", "validation", "installation", "preview-packages"] diff --git a/website/docs/zh/guide/distribution/index.mdx b/website/docs/zh/guide/distribution/index.mdx new file mode 100644 index 000000000..cae908dd1 --- /dev/null +++ b/website/docs/zh/guide/distribution/index.mdx @@ -0,0 +1,51 @@ +--- +description: 'agent-bundle 项目如何变成宿主可以安装的东西:构建、校验,并把每个 target 目录作为可独立分发的捆绑包交付。' +--- + +# 交付捆绑包 + +`agent-bundle build` 让每个 target 目录都**可独立分发**。构建之后没有额外的打包步骤,也没有逐宿主的 +重新打包脚本:target 目录就是你复制、发布或交给宿主 CLI 的那个单位。 + +```sh +npx agent-bundle build --root . --output artifact +``` + +## 流水线 + +| 步骤 | 命令 | 产出什么 | +| --- | --- | --- | +| 构建 | `agent-bundle build` | 每个所选 target 一个目录;当项目声明了 `bin`/`lib` 时,还有 `dist/` 包构建。 | +| 校验 | `agent-bundle validate --artifact --strict` | 内容寻址地证明输出字节与清单一致,外加在宿主发布了校验器时的宿主工具结论。 | +| 安装 | `agent-bundle install --from ` | 把捆绑包注册进 Claude、Codex 或 Cursor。 | + +构建在写出任何东西之前就已经校验过项目,因此针对源码单独运行一次 `validate` 更像是快速的预检,而不是 +必需的阶段。真正有意思的是校验**产物**,因为它完全不需要项目源码。 + +## 一个 target 目录里发布了什么 + +每个已构建 target 都包含一份生成的 `INSTALL.md`,其中使用捆绑包**真实的**插件名与市场名——而不是 +占位符——因此这份文件可以逐字照做。多宿主的 `plugin` target 还带有一份生成的 `AGENTS.md`,说明如何把它 +安装到各个宿主。 + +Claude 与 Codex target 始终包含本地市场清单,正是这一点让它们的公开 CLI 能够直接安装输出的目录。 +Cursor、portable 与组合 target 则包含一个独立的 `install.mjs`,因为 Cursor 提供了市场管理能力,却没有 +非交互式的插件安装动词。 + +`agent-bundle.manifest.json` 记录每个输出文件及其 SHA-256,因此校验比对的是真实字节,而不是检查某个 +路径是否存在。各 target 自身的布局在[项目结构](../start/project-structure.mdx)中介绍。 + +## 面向 npm 的那一半 + +同时作为 npm 包发布的项目还有第二份输出:`dist/bin/.js` 可执行文件与一个库入口,见 +[包入口](../authoring/package-entries.mdx)。当包输出与至少一个宿主包被一起构建时,框架还会输出一个 +相对包的安装器 bin ——见[宿主安装](./installation.mdx)。 + +`agent-bundle prepack` 是这一半的门禁:它运行发布构建、以不执行脚本的方式 dry-run `npm pack`,并核实 +打包后的输出、产物哈希、bin 与版本号。 + +## 本章内容 + +- [产物校验](./validation.mdx) —— 源码与产物校验,以及各宿主自有工具的贡献。 +- [宿主安装](./installation.mdx) —— 安装到 Claude、Codex 与 Cursor,以及各自接受的安装作用域。 +- [预览包](./preview-packages.mdx) —— 目前代替 npm 的 pkg.pr.new 发布通道。 diff --git a/website/docs/zh/guide/distribution/installation.mdx b/website/docs/zh/guide/distribution/installation.mdx new file mode 100644 index 000000000..f3b34ce5c --- /dev/null +++ b/website/docs/zh/guide/distribution/installation.mdx @@ -0,0 +1,99 @@ +--- +description: '把已构建的 agent-bundle 安装进 Claude Code、Codex 或 Cursor:install 命令、宿主作用域、独立的 install.mjs、相对包的安装器 bin,以及 Doctor。' +--- + +# 宿主安装 + +每个已构建 target 目录都包含一份生成的 `INSTALL.md`,其中的命令使用捆绑包真实的插件名与市场名。框架 +CLI 执行的正是同样的操作: + +```sh +npx agent-bundle install claude --from artifact/claude --scope user +npx agent-bundle install codex --from artifact/codex +npx agent-bundle install cursor --from artifact/cursor +``` + +`--from` 既接受一个 target 捆绑包目录,也接受一个**不含源码的产物根目录**,只要该根目录包含所选宿主的 +target 目录。 + +## 各宿主接受什么 + +| 宿主 | 机制 | 作用域 | +| --- | --- | --- | +| Claude Code | 委托给 `claude plugin marketplace add` 与 `claude plugin install`。 | `user`、`project`、`local` | +| Codex | 委托给 `codex plugin marketplace add` 与 `codex plugin add`。 | `user` | +| Cursor | 把捆绑包复制到 `~/.cursor/plugins/local/`,因为 Cursor 未发布非交互式安装动词。 | `user` | + +由于 Claude 与 Codex target 始终随行本地市场清单,它们的公开 CLI 可以直接安装输出的目录。当所选宿主 +二进制文件不可用时,安装器会以一条带类型的诊断失败,而不是报告一次它并未完成的成功。宿主安装诊断属于 +`AB700x` 家族:捆绑包标识、宿主可用性、作用域、命令失败与冲突检查。 + +## 独立安装器 + +Cursor、portable 与组合 target 包含一个 `install.mjs`,它把捆绑包复制到 +`~/.cursor/plugins/local/`,且不会覆盖冲突内容: + +```sh +npx agent-bundle install cursor --from artifact/cursor +# 或者,在输出的 target 目录中执行: +node ./install.mjs +``` + +它的分阶段复制对内容相同的情况是幂等的,并会拒绝版本或内容冲突。它绝不调用 `sudo`,也绝不修改 `PATH`。 +产物校验会拒绝缺少必需安装表面的内置 target,因此捆绑包不可能在缺少它所承诺的安装器的情况下发布。 + +## 相对包的安装器 bin + +当包输出随行上述某个宿主包时,构建还会输出一个相对包的安装器 bin。当没有配置的 bin 占用该名字时,它使用 +插件名,否则使用 `-install`(两者都被占用时追加数字后缀)。请在 `package.json` 中把这个 +名字映射到生成的 `dist/bin/*.js` 文件;消费者随后运行: + +```sh + install [--scope ] [--json] +``` + +帮助信息只列出实际构建出来的宿主。该可执行文件通过 `import.meta.url` 在已安装包旁边定位产物目录,绝不 +依赖调用方的工作目录,因此无论从哪里调用,它在 `node_modules` 中都能工作。**任何 npm 生命周期都不会执行 +安装**——安装一个包绝不会改动宿主的插件状态。 + +## 开发期安装是另一回事 + +`agent-bundle dev --install-host ` 维护的是一个*标记为开发用*的安装,它跟随成功的重建 epoch, +带有原子的世代切换与一条稳定的 proxy 命令。这部分内容在 +[开发者 Workbench](../development/workbench.mdx)中,与 `agent-bundle install` 不是同一个操作。 + +## 重建后重新安装 + +每个输出的安装器——`agent-bundle install `、相对包的 bin 与独立的 `install.mjs`——共用同一套替换策略。 +内容完全相同的副本是 `already-installed` 空操作。**版本相同但内容哈希不同**的副本会被自动替换,因此不升版本 +地重建不再需要卸载加 `rm -rf`。版本不同则以 `AB7005` 拒绝,除非传入 `--replace`(别名 `--force`);外来目录 +——不是本插件安装器放置的——无论如何都会被拒绝。Cursor 副本携带安装回执(`.agent-bundle-install.json`: +插件、版本、宿主、内容哈希、归属文件);替换就地进行,只触碰归属文件,绝不动 `state/` 之类的非归属条目, +`--replace` 会接管回执出现之前的副本。Claude 的替换先运行 `claude plugin uninstall --keep-data` 再重新安装, +因为 `plugin update` 受版本门控;Codex 先 `codex plugin remove` 再 `add`。输出的 `INSTALL.md` 按宿主记录了 +同样的步骤。 + +## 在不改动的前提下检查安装 + +```sh +npx agent-bundle doctor --host claude --host cursor --json +npx agent-bundle doctor --from artifact --json +``` + +Doctor 是只读的。它探测宿主、清点已安装的捆绑包、把它们与提供的捆绑包做比对、检查注册证明、采样运行时 +端点的健康状况与身份、清点持久状态,并对已安装的字节重新运行被固定的、无进程的文档与加载器校验器。它 +绝不修复任何东西。带 `--from` 时,它按宿主把已安装副本报告为 `current`、`stale`(`AB7308`)、`version-mismatch` +(`AB7309`)、`foreign`(`AB7321`)或 `not-installed`(`AB7307`)。 + +| 代码 | 严重级别 | 含义 | +| --- | --- | --- | +| `AB7316` | warning | 某个已安装捆绑包的 `state/` 目录,或它的某个 `*.sqlite`、`-wal`、`-shm` 文件无法通过文件系统元数据操作读取。Doctor 只按目录条目与元数据清点状态;它绝不打开数据库。 | +| `AB7317` | info | 某个活跃的事件运行时实现的是较旧的严格协议,不暴露运行时身份。 | +| `AB7318` | error | 在有界的只读身份探测过程中,某个活跃的事件运行时变为不可用、超时,或返回了无效的状态响应。 | +| `AB7319` | error | 由 `doctor --from` 解析出的宿主目录树违反了它被固定的文档 schema 或无进程加载器规则;消息中保留原始的构建校验器代码。 | +| `AB7320` | error / info | 当某个 `.cursor-plugin/plugin.json` 安装违反 Cursor 被固定的文档 schema 或 token 位置规则,或某个本地插件包含逃逸出 `~/.cursor/plugins/local` 的符号链接时为 error,该清点条目被报告为 `corrupt`。当某个 `.claude-plugin/plugin.json` 或根 `plugin.json` 安装没有 Cursor 侧被固定的静态文档契约时为 info,被加载器识别的条目仍为 `installed`。 | + +## 下一步 + +- [预览包](./preview-packages.mdx) —— 目前可安装的 tarball 从哪里来。 +- [产物校验](./validation.mdx) —— 在安装之前为捆绑包把关的那些检查。 diff --git a/website/docs/zh/guide/distribution/preview-packages.mdx b/website/docs/zh/guide/distribution/preview-packages.mdx new file mode 100644 index 000000000..9b24dc8b0 --- /dev/null +++ b/website/docs/zh/guide/distribution/preview-packages.mdx @@ -0,0 +1,61 @@ +--- +description: '代替 npm 的 pkg.pr.new 预览通道:预览包如何发布、如何固定版本,以及首个 npm 版本发布后会发生什么变化。' +--- + +# 预览包 + +目前尚未向 npm 发布任何内容,这是刻意为之。现有的包名只是占位符,npm 发布被推迟到最终包名确定之后。 +在那之前,**pkg.pr.new 就是发布通道**:每次 CI 的 package-preview 运行都会把全部三个可发布的工作区包 +以真实、可安装的 tarball 形式发布到一个按 commit SHA 与 pull request 索引的免费持续发布注册表。 + +安装命令与 runtime 配对规则在[安装](../start/installation.mdx)中。本页讲的是通道本身:预览包从哪里来、 +你能信任它到什么程度,以及固定版本究竟保证了什么。 + +## 预览包从哪里来 + +`.github/workflows/package-preview.yml` 在一次完整构建之后运行 `pnpm preview:publish`,覆盖每个 +pull request 与每次推送到 `main`。它以 `--previewVersion --peerDeps --no-compact --no-template` +发布 `packages/agent-bundle`、`packages/rsc-runtime` 与 `packages/create-agent-bundle`。 + +针对 `main` 推送的运行使用**逐提交**的并发分组,因此相互重叠的推送不会取消彼此,每个 `main` 提交都有 +一份可安装的快照。PR 的运行则会取消同一个 PR 中被取代的构建,因为只有 PR 的最新一次预览才有意义。 + +PR 或提交上的「Publish pkg.pr.new preview」检查会链接到该次构建的确切 URL。预览包由发布门禁所校验的 +同一份 `pnpm build` 输出构建而来——但它们不是 npm 正式版本,并且携带预览版本号。 + +## 固定版本,以及一条历史遗留注意事项 + +配对规则与安装命令在[安装](../start/installation.mdx)中;有两个细节属于通道本身。 + +PR 引用会解析到**该 PR 最近一次发布的预览**,因此 `@1` 表示「PR #1 最新一次构建」,而不是某个固定 +提交。短 SHA 永远不会移动,这正是 lockfile 应当携带它的原因。 + +在 peer 改写落地(PR #46)之前发布的预览包仍携带原始的 `agent-bundle@^0.1.0` peer 范围,因此用 npm +配对安装那些较早的 SHA 仍然需要 `--legacy-peer-deps`。更新的预览包用原生 npm 即可安装。 + +脚手架发布在同一条通道上,其设计意图是直接运行而不是安装: + +```sh +npx https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@ my-plugin +``` + +脚手架生成的项目会把 `agent-bundle` 固定到脚手架自身所来自的那个提交的预览版本,因此上述配对规则的 +两侧会自动成立。 + +## 首个 npm 版本发布后会发生什么变化 + +首个 npm 版本将使用 +[npm package provenance](https://docs.npmjs.com/generating-provenance-statements):发布步骤会导出 +`NPM_CONFIG_PROVENANCE=true`,并在 `changeset publish` 之前运行打包发布门禁。 +`publishConfig.provenance` 已经设置好了。 + +在启用这条路径之前,发布负责人必须解决两件事:最终的包名与许可证,以及 `agent-bundle` 仓库层面的 +`"access": "restricted"` 策略——目前它并没有被 `publishConfig.access` 覆盖。 + +`pnpm release` 会在发布之前运行发布门禁 —— `pnpm pack:dry-run`、`pnpm audit:release` 与 +`pnpm test:packed:release`。该门禁仅用于发布,并不替代日常的 `pnpm check` 交付门禁。 + +## 下一步 + +- [安装](../start/installation.mdx) —— 确切的安装与配对命令。 +- [产物校验](./validation.mdx) —— 这些预览包所经过的 `prepack` 清单门禁。 diff --git a/website/docs/zh/guide/distribution/validation.mdx b/website/docs/zh/guide/distribution/validation.mdx new file mode 100644 index 000000000..5cfc7a357 --- /dev/null +++ b/website/docs/zh/guide/distribution/validation.mdx @@ -0,0 +1,115 @@ +--- +description: '校验 agent-bundle 源码与已构建产物,包括严格的 Claude Code 宿主校验、被固定的 Codex 与 Cursor schema 检查,以及 npm prepack 门禁。' +--- + +# 产物校验 + +校验用同一条命令回答两个不同的问题。针对**源码**,它问的是项目是否描述了一个合法的捆绑包;针对 +**产物**,它问的是输出的字节是否就是清单所承诺的字节。 + +```sh +npx agent-bundle validate --root . # 项目源码 +npx agent-bundle validate --artifact artifact --strict # 已构建字节,无需源码 +``` + +`validate --artifact`、`mcp` 与 `hooks` 都能在项目源码已被删除的情况下针对已构建产物工作。这是刻意 +如此:只有在源码树存在时才能通过校验的产物,其实并不算自包含;[测试](../development/testing.mdx)中的 +`packed-deleted-source` 证明级别存在的理由与此相同。 + +## 内容寻址,而不是路径存在性 + +`agent-bundle.manifest.json` 为每个输出文件记录一份 SHA-256 摘要,包括被复制的脚本与资源。产物校验 +把真实字节与这些摘要比对,因此被手工改过的生成文件会失败,而不会因为路径还在就通过。被引用的文件同样 +会被检查——清单声明的 `logo` 若在产物中缺失或逃逸出部署树,会报告 `AB6025`。 + +每条诊断都是一份结构化记录:稳定的 `AB` 代码、一个严重级别、一条消息,通常还有 `sourcePath` 与一条 +`recovery` 提示。由诊断把关的命令——`build`、`prepack`、`validate`、`doctor`、`install` 与 `dev`——只有 +存在 error 级诊断时才以非零退出;warning 与 info 绝不会为构建、校验或 dev 重建把关。`eval` 与 `inspect` +在试验失败或无定论、或模型无效时也会以 `1` 退出,这与错误诊断无关——见 +[命令行退出码](../../reference/cli.mdx#退出码)。 + +## Claude Code 严格校验 + +当 Claude Code 位于 `PATH` 上时,产物校验会为输出的 `claude` target 以及统一的 `plugin` target 运行 +`claude plugin validate --strict`: + +- 宿主的 **error** 会变成 Agent Bundle 的 error。 +- 宿主的 **warning** 保持为 warning,除非设置了 `agent-bundle validate --strict`。 +- 二进制文件缺失会被报告为一次显式的信息性**跳过**,绝不会伪造成功。 + +CI 应当使用严格校验: + +```sh +npx agent-bundle validate --artifact artifact --strict +``` + +当需要确定性的、只看 schema 的检查时使用 `--no-host-validation` ——没有安装这些宿主的机器也必须能运行 +同一道门禁,并得到同样的 schema 结论。 + +在开发期,你可以在不安装的情况下加载一个已构建 target 并核实注册情况: + +```sh +claude --plugin-dir artifact/claude plugin list --json +``` + +## Codex 与 Cursor:被固定的 schema + +Codex 与 Cursor 目前都没有发布插件校验的开发者动词,因此 Agent Bundle 用自己 vendored 的、被固定的 +schema 校验它们输出的 JSON 文档,并把宿主的 schema 生成器视为一个**漂移信号**,绝不当作替代的插件契约。 + +| 代码 | 严重级别 | 含义 | +| --- | --- | --- | +| `AB6026` | info | 每份 Cursor 报告都会声明 Cursor 未发布 plugin-validate 动词,并指明本地校验所用的 vendored schema 固定版本。 | +| `AB6027` | error | 某个必需的生成式 Cursor 文档缺失,或者已存在的 plugin、市场、MCP 或 hooks 文档不可读、不是合法 JSON,或被其固定 schema 拒绝。 | +| `AB6028` | error | 生成的字节违反了被固定的 Cursor 加载器证据:清单候选优先级选中了回退清单、符号链接解析到捆绑包之外,或者 `CURSOR_PLUGIN_ROOT` 出现在加载器替换字段之外。 | +| `AB6029` | info / warning | Cursor Agent 版本探测不可用(`ENOENT`,info),或未能完成(warning)。本地的固定 schema 校验仍会运行。 | +| `AB6030` | info | Codex CLI 不可用,或已安装的 Codex 版本未发布插件校验命令。 | +| `AB6031` | info / warning(`--strict` 下为 error) | Codex 的 app-server schema 生成动词不可用,或其实时输出缺失、与被固定的生成式钩子 schema 不一致。 | +| `AB6032` | error | 某个必需的 Codex 捆绑包文档缺失、不可读、不是合法 JSON,或未通过其 vendored 固定 schema。 | +| `AB6033` | error | 一条有界的 Codex 版本或 schema 生成命令无法启动、执行失败、超时、输出超过 1 MiB,或产生了不可读的输出。 | + +`AB6031` 是其中最值得注意的一条:当 Codex 生成的 schema 与被固定的版本不一致时,只有在 Codex 发布了 +匹配的契约之后才会更新这个固定版本。自动跟随宿主的实时输出,会把一次上游变更变成这里一次无声的契约变更。 + +## Agent Skills 规范 lint + +`AB6034` 是针对输出字节的 **error**:某份 `SKILL.md` 拥有合法的 YAML frontmatter,但其后没有 Markdown +指令正文。被固定的 Agent Skills 规范要求 frontmatter 之后必须跟随 Markdown 内容,而这项检查针对的是 +产物实际包含的内容,而不是编写时的源码。 + +## 哈希固定与仓库自有的表格 + +哈希固定覆盖那些真实来源位于本仓库之外、且可能漂移的 vendored 外部内容:`src/adapters/schemas/*` 下的 +宿主文档 schema(其上游 URL、commit 与 SHA-256 记录在 `PROVENANCE.json` 中)、清单 `agentSkills` 块中 +由 Agent Skills 规范派生的 schema 固定值,以及为完整性而记录的输出产物文件与源输入。 + +仓库自有的能力表格与证据**不**做哈希。能力证据记录被观察到的宿主版本(`observedVersion`)与 target, +适配器则携带一个单调递增的 `adapterRevision`。Git 已经为仓库自有内容做了版本管理;在仓库内部再哈希一遍 +是自我指涉的,并且会让每次编辑表格都产生变动噪声。 + +## npm prepack 门禁 + +```sh +npx agent-bundle prepack --root . --output artifact --json +``` + +`prepack` 运行发布构建与 `npm pack --dry-run --json --ignore-scripts`,随后对精确的包与产物清单、清单 +哈希、包 bin 目标以及发布版本一致性把关。`--output` 是相对 `--root` 的产物路径,会覆盖配置中的 +`output.distPath`,默认值为 `artifact`。把它用作 npm 的 `prepack` 脚本;`--ignore-scripts` 可防止递归, +而且任何 npm 生命周期都绝不会执行宿主安装。 + +| 代码 | 含义 | +| --- | --- | +| `AB7010` | dry-run 的 npm 清单遗漏了某个包输出、产物清单或文件、安装表面,或 README。请把 `dist` 与产物目录加入包的 `files` 允许列表。 | +| `AB7011` | 磁盘上的某个产物文件与其清单 SHA-256 不再匹配。请重新构建,且不要修改生成的宿主包。 | +| `AB7012` | 某个 `package.json` bin 指向了打包后的 `dist` 输出之外(包括 `src/`),或指名了一个被 npm 忽略的文件。请把它指向生成的 `dist/bin` 文件。 | +| `AB7013` | `package.json`、规范化后的插件元数据、某份宿主清单或产物 provenance 报告了不同的发布版本。请让每处发布标识一致。 | + +发布构建同样会拒绝**完全没有**发布版本的项目(`AB4013`),因此已发布的产物绝不会携带 +`0.0.0-dev.` 这个开发期回退值。声明的 `plugin.version` 与 `package.json` 不一致时会 +报告 `AB4008` 警告。 + +## 下一步 + +- [宿主安装](./installation.mdx) —— 把已校验的捆绑包注册到宿主。 +- [预览包](./preview-packages.mdx) —— 这些输出目前如何抵达消费者。 diff --git a/website/docs/zh/guide/start/_meta.json b/website/docs/zh/guide/start/_meta.json new file mode 100644 index 000000000..7d40fbbbd --- /dev/null +++ b/website/docs/zh/guide/start/_meta.json @@ -0,0 +1 @@ +["index", "installation", "quick-start", "project-structure"] diff --git a/website/docs/zh/guide/start/index.mdx b/website/docs/zh/guide/start/index.mdx new file mode 100644 index 000000000..8d44f8e30 --- /dev/null +++ b/website/docs/zh/guide/start/index.mdx @@ -0,0 +1,86 @@ +--- +description: 'agent-bundle 将一份带类型的配置编译为可安装到 Claude Code、Codex、Cursor 的插件产物以及可移植布局。' +--- + +# 介绍 + +agent-bundle 把一个智能体插件——由一份带类型的配置描述的 Skills、钩子、MCP 服务器与脚本——编译为 +可安装到 Claude Code、Codex 与 Cursor 的产物,外加一份可移植布局。插件只写一次,编译器负责生成 +每个宿主各自的清单与包装层。 + +需要 Node.js 22.19 或更高版本。 + +## 它解决的问题 + +每个智能体宿主都想要同一个插件、但要按自己的布局表达:自己的清单文件名、自己表示插件安装根目录的 +占位符写法、自己的钩子文档形状、自己的 MCP 服务器声明。手工维护这些,等于把同一个插件维护好几遍, +而且只有在安装之后才会发现它们互相不一致。 + +agent-bundle 把这件事反转过来。宿主专属布局是编译器的职责,因此它不会出现在你的源码树中: + +```sh +npx agent-bundle build --root . +``` + +当 `targets: ['plugin']` 时,这一条命令会在 `artifact/plugin/` 生成一份多宿主捆绑包:`.claude-plugin/`、 +`.codex-plugin/` 与 `.cursor-plugin/` 三份清单,共享同一套 `skills/`、`hooks/`、`mcp/` 与 `scripts/` +目录。捆绑包中生成的 `AGENTS.md` 会说明如何把它安装到各个宿主。单宿主布局则由 `claude`、`codex`、 +`cursor` 与 `portable` 这几个 target 提供。 + +## 配置负责什么 + +项目根目录下的一份 `agent-bundle.config.ts` 描述整个插件: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { name: 'my-plugin', description: 'What it does.' }, // [!code highlight] + targets: ['plugin'], + skills: ['src/skills/*'], + hooks: { sessionStart: { handler: './src/session-start.ts' } }, + mcp: { servers: { tools: { entry: './src/mcp.ts' } } }, +}); +``` + +同一份配置还负责 npm 包构建——不需要第二份打包器配置、不需要 bin 垫片、也不需要手写 stdio 生命周期。 +`bin` 与 `lib` 条目(或 `src/cli.ts`、`src/index.ts`、`src/mcp/.ts` 这几个约定)会在宿主 +产物之外一并生成可执行的 `dist/bin/.js` 包与库输出。默认导出服务器工厂函数的 MCP 入口会运行在 +框架自有的 stdio 生命周期之下。`tools.rsbuild` / `tools.rspack` 是唯一的打包器逃生舱。 + +## 编写模型 + +agent-bundle 只有一个面向新手的模型,四行就能写完: + +1. **编写的源码放在 `src/` 下。** Skills、命令、规则、脚本、MCP 路由、状态与 provider 都有各自约定的 + `src/` 根目录。路径即身份:位于 `src/mcp/curator/tools/status.tsx` 的模块*就是* `curator` 服务器的 + `status` 工具。 +2. **一份小而扁平的配置。** `agent-bundle.config.ts` 只保存项目标识、targets,以及任何单个路由文件都 + 无法拥有的策略。 +3. **JSX 意味着渲染。** 一个可执行路由就是一个 async 默认导出的 Server Component:它完成工作并返回 + `Agent.*` 节点。不存在公开的 `execute`/`render` 分裂。 +4. **按需接入上下文。** 只有在需要 host、session、actor、workspace、capability 或 state 上下文时, + 才在该组件内部调用 `await agent()`。 + +这条线以上的内容都属于进阶参考:自定义与远程 MCP 服务器模式、预构建 payload、请求上下文 provider, +以及打包器逃生舱。 + +## 证据,而不是感觉 + +能构建的插件不等于能工作的插件。agent-bundle 提供彼此独立的证明级别——route-unit、内存内 MCP、 +CLI 派发、打包后的 stdio、删除源码后的打包运行,以及宿主安装——每个辅助函数都会把自己所承载的级别 +写进 provenance。某一级别的通过绝不会被当作另一级别的凭据;当断言所需的证据强于该 harness 实际产生的 +证据时,结果是 `inconclusive`,而不是悄悄通过。 + +## 下一步 + +- [安装](./installation.mdx) —— 安装 CI 目前发布的预览包。 +- [快速开始](./quick-start.mdx) —— 使用脚手架,或手写配置。 +- [项目结构](./project-structure.mdx) —— 约定的 `src/` 根目录与输出布局。 +- [编写](../authoring/index.mdx) —— 配置模型与每一种可编写的表面。 + +仓库中对同样的契约有更深入的说明: +[Framework mode](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/framework-mode.md) +用一屏讲完整个编写模型, +[Entry conventions](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/entry-conventions.md) +则是完整的包构建契约。 diff --git a/website/docs/zh/guide/start/installation.mdx b/website/docs/zh/guide/start/installation.mdx new file mode 100644 index 000000000..d0b0518c3 --- /dev/null +++ b/website/docs/zh/guide/start/installation.mdx @@ -0,0 +1,81 @@ +--- +description: '从 pkg.pr.new 预览通道安装 agent-bundle,以及首个 npm 版本发布后会发生什么变化。' +--- + +# 安装 + +## 环境要求 + +- **Node.js 22.19 或更高版本**,用于运行编译器与开发者 Workbench。 +- **一个包管理器** —— npm、pnpm、yarn 与 bun 都可以;下面的示例使用 npm。 + +生成的可执行文件默认以 Node.js 22.12 及以上为目标。`runtime: { node: '24.0' }` 可以抬高这个下限 +(永远无法降低),所选下限会以 `runtime.node` 记录在产物清单中。 + +## 从预览通道安装 + +目前尚未向 npm 发布任何内容。这是刻意为之:现有的包名只是占位符,而 npm 上的 `agent-bundle` 这个名字 +当前属于一个无关项目。在首个正式版本发布之前,请安装 CI 为每个提交和每个 pull request 发布的预览包: + +```sh +npm i -D https://pkg.pr.new/ScriptedAlchemy/agent-bundle/agent-bundle@ +``` + +使用 PR 编号,或使用某个 package-preview 运行成功的提交 SHA —— 每个绿色的 `main` 提交都有一个。 +短 SHA 同样可用;对于 lockfile 与可复现的环境,SHA 是正确的形式,而 PR 编号则会跟踪该 pull request +的最新一次构建。 + +### 从同一个提交配对安装 runtime 包 + +带有路由模块的项目还需要 `@agent-bundle/runtime`,它发布在同一个通道: + +```sh +npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/agent-bundle@5685521 +npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@5685521 +``` + +两者必须来自**同一个** SHA 或 PR 编号。`agent-bundle` 把 `@agent-bundle/runtime` 声明为可选 peer +依赖(runtime 包本身不声明 `agent-bundle` peer),发布过程(`--peerDeps`)会把 `agent-bundle` 预览包 +内部的这个 peer 范围改写为同一提交构建出的 runtime 精确预览版本,因此配对安装用原生 npm 即可完成。 +混用两个不同的 SHA 会按设计以 `ERESOLVE` 失败——需要检查的范围位于已安装的 `agent-bundle` 包中。 + +pnpm 与 yarn 接受同样的 URL(`pnpm add `、`yarn add agent-bundle@`)。 + +预览版本号形如 `0.0.0-preview-`。它们由发布门禁校验的同一份产物构建而来,但并不是 npm 正式版本。 +关于固定版本的细节以及预览包的来源,见 +[Preview packages](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/preview-packages.md)。 + +## 可选依赖 + +只有在项目用到对应表面时才需要安装: + +| 包 | 用于 | +| --- | --- | +| `@agent-bundle/runtime` | 路由模块、渲染式 Skill,以及渲染式 CLI 命令或脚本。生成的入口与你的代码以同样的方式导入它。 | +| `@rstest/core` | 运行框架测试 harness。它是可选 peer 依赖,因此从不测试路由的项目也不会安装它。 | +| `react` | 渲染路由模块并对其断言。同样是可选 peer 依赖。 | +| `typescript` | `lib.dts` 声明生成,它会从项目中解析 `typescript`。 | + +## 等到 npm 正式发布之后 + +下面这些命令是**未来**的安装路径。它们现在还不可用,因为尚未发布任何 npm 版本: + +```sh +npm install --save-dev agent-bundle +npm create agent-bundle@latest my-plugin +``` + +首个 npm 版本将使用 npm package provenance。在发布负责人确定最终包名与许可证之前,pkg.pr.new 预览 +就是发布通道。 + +## 验证安装 + +```sh +npx agent-bundle --help +npx agent-bundle validate --root . +``` + +`validate` 检查项目源码。构建出产物之后,改用 `validate --artifact ` 检查已构建的产物,此时 +完全不需要项目源码。 + +下一步:[快速开始](./quick-start.mdx)。 diff --git a/website/docs/zh/guide/start/project-structure.mdx b/website/docs/zh/guide/start/project-structure.mdx new file mode 100644 index 000000000..859ea882a --- /dev/null +++ b/website/docs/zh/guide/start/project-structure.mdx @@ -0,0 +1,162 @@ +--- +description: 'agent-bundle 识别的约定 src/ 根目录、配置与约定之间的关系,以及构建输出的落点。' +--- + +# 项目结构 + +一个 agent-bundle 项目就是普通的 Node 包,只是在根目录多了一个文件,并采用约定的 `src/` 目录树。 +这里没有任何东西是强制的:配置沉默时由约定补齐,而当两者描述同一件事时,配置总是胜出。 + +## 目录布局 + +```text +my-plugin/ +├── agent-bundle.config.ts # 项目标识、targets 与策略 +├── package.json # 权威的发布版本号与包标识 +├── assets/ # 按字节复制到每个产物中的静态文件 +└── src/ + ├── skills//SKILL.md # 每个目录一个 Skill,并带有自己的资源 + ├── commands/*.md # 宿主斜杠命令文档 + ├── rules/*.mdc # 宿主规则文档 + ├── hooks/*.ts # 由配置引用的生命周期钩子处理器 + ├── mcp/.ts # 手写的 stdio MCP 服务器入口 + ├── mcp// # 或生成式服务器,每个路由一个模块 + │ ├── tools/*.tsx + │ ├── resources/*.tsx + │ ├── prompts/*.tsx + │ ├── apps/*.tsx # 编译为自包含 HTML 的浏览器 MCP App + │ └── layout.tsx # 可选的按服务器布局,包裹该服务器的路由 + ├── scripts/.ts # 产物脚本(.tsx 通过 Agent 渲染器渲染) + ├── cli.ts # 单个包 bin + ├── cli/**/*.ts # 或路由式 CLI,嵌套即命令路径 + ├── index.ts # 库入口 + ├── layout.tsx # 可选的共享布局,包裹每个渲染式路由 + ├── state.ts # 项目状态定义 + └── providers/.ts # 请求上下文 provider +``` + +## 各个根目录的含义 + +| 路径 | 表面 | 如何退出 | +| --- | --- | --- | +| `src/skills//SKILL.md` | 一个 Skill。目录中其余内容都作为它的资源随行。完全不需要任何声明即可随产物发布。 | 删除该目录,或收窄 `skills` 配置中的 glob。 | +| `src/commands/*.md` | 扁平的宿主命令文档。 | 删除该文件。 | +| `src/rules/*.mdc` | 扁平的宿主规则文档。 | 删除该文件。 | +| `src/mcp/.ts` | 某个已声明、但未指定 `entry`、`command` 或 `url` 的 MCP 服务器的 stdio 入口。 | 显式声明 `entry`。 | +| `src/mcp//{tools,resources,prompts}/*` | 生成式 MCP 服务器路由。路径提供身份;每个模块提供静态 `config`、schema,以及一个 async 默认 Server Component。 | 把 `routes.servers.` 设为 `custom`、`command` 或 `remote`。 | +| `src/mcp//apps/*` | 浏览器 MCP App 入口,编译为自包含 HTML 并注册到生成的服务器上。必须提供静态 `config.resourceUri`。 | 使用自定义服务器,或给文件名加 `_` 前缀。 | +| `src/scripts/.ts` | 一个普通脚本,在每个所选 target 中编译为 `scripts/.mjs`。嵌套模块是硬错误(`AB4808`)。 | 给某一段路径加 `_` 前缀,或用显式 `scripts` 条目认领该文件。 | +| `src/scripts/.tsx` | 渲染式脚本:async 默认组件接收 `argv` 与 `signal`,并按 CLI 输出契约通过 Agent 渲染器渲染。 | 改名为 `.ts`、给某一段路径加 `_` 前缀,或认领该文件。 | +| `src/cli.ts` | 一个以 `plugin.name` 命名的包 bin。 | `bin: false` | +| `src/cli/**/*.{ts,tsx}` | 路由式 CLI 命令,编译进一张做过冲突检查的命令图与一个可执行文件。嵌套即身份:`src/cli/library/audit.ts` 以 ` library audit` 运行。它取代 `src/cli.ts` 约定。 | `bin: false`、`routes.cli: 'conventional'`,或给某一段路径加 `_` 前缀。 | +| `src/index.ts` | 库输出,带声明文件。 | `lib: false` | +| `src/layout.{ts,tsx}` | 共享文档布局:默认导出一个接收 `{ children, route, signal }` 的组件,在每个渲染式路由——生成式 MCP 工具、资源与提示、渲染式路由 CLI 命令、投影的 MCP 命令与渲染式脚本——外层渲染 `Agent.Result`。事件路由与浏览器 App 永不被包裹。 | 重命名为 `_layout.tsx`。 | +| `src/mcp//layout.{ts,tsx}` | 按服务器的布局,嵌套在根布局之内,包裹该生成式服务器的路由。 | 重命名为 `_layout.tsx`,或把 `routes.servers.` 设为非生成模式。 | +| `src/state.ts` | 项目状态:默认导出 `defineState`。生成的 MCP、路由式 CLI 与渲染式脚本的请求作用域都会挂载它。 | `state: false`,或改名为 `_state.ts`。 | +| `src/providers/.{ts,tsx}` | 一个请求上下文 provider,挂载在请求句柄的 `providers.` 上。 | 给文件名加 `_` 前缀。 | +| `assets/` | 静态资源,按字节复制到每个 target 产物的 `assets/` 目录。 | 改为声明顶层 `assets` 列表。 | + +路由与包入口约定精确匹配 `.ts` 与 `.tsx` 文件;state 约定则专指 `src/state.ts`。被发现的条目在 +规范化模型中带有 `provenance.kind: 'conventional'`,因此 `agent-bundle inspect` 能告诉你某个文件 +是被约定识别的,还是被配置认领的。 + +## 配置与约定 + +配置保存任何单个文件都无法拥有的内容——项目标识、target 选择与策略: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { description: 'Evidence-backed project tools.', name: 'my-plugin' }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +只有当你需要约定无法表达的东西时才添加显式声明——不同的路径、target 限制,或退出约定: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { description: 'Evidence-backed project tools.', name: 'my-plugin' }, + scripts: { + // Restricted to one target, so it cannot ride the convention. + 'detect-risk': { entry: './src/scripts/detect-risk.ts', targets: ['portable'] }, + }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +当项目呈现出前约定时代的写法时,源码校验会报告**信息级**提示,而不是错误:`AB4730` 对应一个自行连接 +传输层的 stdio 入口(改为默认导出工厂函数即可升级到框架生命周期外壳),`AB4731` / `AB4732` / +`AB4733` 对应 `src/cli.ts`、`src/index.ts` 或 `src/mcp/.ts` 存在、但被显式配置遮蔽的情形。 +`bin: false` 与 `lib: false` 这两个退出方式则完全静默。 + +## 输出落在哪里 + +`agent-bundle build` 会写出两类彼此独立的东西。 + +### 宿主产物 + +在产物根目录下,每个所选 target 一个目录。命令行把该根目录默认为 `artifact/`,因此它永远不会与下文的包构建 +冲突;`output.distPath` 或 `--output` 可以移动它: + +```text +artifact/ +├── agent-bundle.manifest.json # 每个产出文件及其 SHA-256 +└── plugin/ # targets: ['plugin'] — 一个多宿主捆绑包 + ├── .claude-plugin/ + ├── .codex-plugin/ + ├── .cursor-plugin/ + ├── bin/.mjs # 路由式 CLI,存在 src/cli/** 时出现 + ├── skills/ + ├── hooks/ + ├── mcp/ + ├── scripts/ + ├── assets/ + ├── AGENTS.md + └── INSTALL.md +``` + +单宿主布局由 `claude`、`codex`、`cursor` 与 `portable` 这几个 target 提供。 +`agent-bundle.manifest.json` 位于产物根目录、与各 target 目录并列,记录了每个产出文件及其 SHA-256,因此产物校验是内容寻址的,而不是猜测。 + +`output.distPath` 只移动产物根目录;它从不改变每个 target 内部由框架拥有的布局。优先级是 CLI +`--output`,然后 `output.distPath`,最后是默认值——对同时输出包构建的 `agent-bundle build` 是 +`artifact`,对不带 `packageOutputs` 的编程式 `build()` 是 `dist`。取值必须是非空、限定在项目根目录内的 +相对 POSIX 路径。 + +### npm 包构建 + +当项目声明了 `bin`/`lib`——或通过约定提供了它们——同一次构建还会在 `dist/` 下写出可供 node 消费的 +包构建: + +```text +dist/ +├── bin/.js # 自执行 ESM、shebang、可执行位 +├── .js # 库入口 +└── **/*.d.ts # 声明文件,当 lib.dts 开启时 +``` + +`dist` 是强制忽略的目录:包输出永远不会进入项目源码快照,也不会进入 Skill 与资源发现。两类输出不得重叠: +在带有包入口的项目上把 `output.distPath` 或 `--output` 指向 `dist` 就是 `AB4706`。默认值已经把二者分开, +显式写出也无妨: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + output: { distPath: 'artifact' }, + plugin: { description: 'A CLI plus a plugin.', name: 'my-plugin' }, + targets: ['portable', 'claude'], +}); +``` + +## 下一步 + +- [配置模型](../authoring/index.mdx) —— 完整的配置表面。 +- [Skills](../authoring/skills.mdx)、[钩子](../authoring/hooks.mdx)、 + [MCP 服务器与 MCP App](../authoring/mcp.mdx) —— 每种表面一页。 +- [脚本与资源](../authoring/scripts-assets.mdx) 与 + [包入口](../authoring/package-entries.mdx) —— 其余的构建输出。 diff --git a/website/docs/zh/guide/start/quick-start.mdx b/website/docs/zh/guide/start/quick-start.mdx new file mode 100644 index 000000000..1c96211bb --- /dev/null +++ b/website/docs/zh/guide/start/quick-start.mdx @@ -0,0 +1,119 @@ +--- +description: '用脚手架创建 agent-bundle 项目,或手写 agent-bundle.config.ts,然后构建并运行开发者 Workbench。' +--- + +# 快速开始 + +有两条入门路径。脚手架生成的项目本身就能通过自带的 `check`;手写路径则只需要在已有仓库里加四行配置。 + +## 用脚手架创建项目 + +最快的方式是 `create-agent-bundle`。它会依次询问名称、模板与宿主 targets: + +```sh +npx https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@ my-plugin +``` + +等到 npm 正式发布之后,这条命令会变成 `npm create agent-bundle`。在此之前,请使用 +[预览通道](./installation.mdx)中的提交 SHA 或 PR 编号。 + +同时指定目录与模板的运行会被视为脚本化调用,不再询问任何问题——其余取值回退到各自的默认值: + +```sh +npx https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@ my-plugin \ + --template mcp-server \ + --targets portable,codex,claude +``` + +### 模板 + +| 模板 | 你会得到什么 | +| --- | --- | +| `minimal` | 一个纯 Skills 插件:一个 `src/skills//SKILL.md` 目录,此外别无他物。 | +| `mcp-server` | 由一个 `src/mcp//tools/.tsx` 路由模块构成的 stdio MCP 服务器,外加一个产物脚本,并已接好框架测试 harness。 | +| `cli-tool` | 通过 `src/cli.ts` bin 约定提供的可安装 CLI,外加一个带声明文件的 `src/index.ts` 库导出。 | + +每个模板都自带 `check` 脚本(validate、build、typecheck、tests),并且校验时零诊断——包括 `AB473x` +迁移提示,因为这些模板从一开始就是按照 entry 约定编写的。`mcp-server` 模板还自带消费者测试 harness, +每个测试池都标注了自己承载的证明级别。 + +脚手架生成的项目会把 `agent-bundle` 固定到与脚手架自身同一提交的预览包,因此脚手架与它所固定的框架 +永远不会互相冲突。全部参数见 +[create-agent-bundle README](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/packages/create-agent-bundle/README.md)。 + +## 或者手写配置 + +在项目根目录的 `agent-bundle.config.ts` 中描述插件: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { name: 'my-plugin', description: 'What it does.' }, + targets: ['plugin'], + skills: ['src/skills/*'], + hooks: { sessionStart: { handler: './src/session-start.ts' } }, + mcp: { servers: { tools: { entry: './src/mcp.ts' } } }, +}); +``` + +大多数项目需要的比这还少,因为配置沉默时 `src/` 约定会自动补齐: + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { description: 'Evidence-backed project tools.', name: 'my-plugin' }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +发布版本号来自 `package.json`。`plugin.version` 字段仍然可用,但它是已废弃的兼容轴;取值与 +`package.json` 不一致时会报告 `AB4008` 警告。 + +## 构建,或交互式开发 + +```sh +npx agent-bundle build --root . # 把可安装产物写到 artifact/ +npx agent-bundle dev --root . # 带实时重建的本地 Workbench +``` + +`build` 会校验项目并写出产物;声明了 `bin`/`lib` 时还会一并完成包构建。`dev` 在 loopback 上提供 +开发者 Workbench,并随输入变化持续重建:项目概览与诊断、Skill 文档、带 provenance 与 epoch 对比的 +产物树、绑定到产物、带原始协议轨迹的 MCP playground、运行真实生成包装层的钩子 playground,以及 +eval 运行。 + +## 查看编译器的判断 + +```sh +npx agent-bundle inspect --root . # 规范化配置与逐 target 计划 +npx agent-bundle inspect --root . --skills # 加上 skill focus +npx agent-bundle validate --root . # 检查项目源码 +``` + +`inspect` 读取源码配置并展示规范化后的模型——这正是确认某个约定是否真的被识别的地方。 + +## 安装结果 + +每个构建出的 target 目录中都包含生成的 `INSTALL.md`,其中的命令使用该捆绑包真实的插件名与市场名。按上文构建出 +`portable`、`codex` 与 `claude` 三个 target 后,宿主安装命令是: + +```sh +npx agent-bundle install claude --from artifact/claude --scope user +npx agent-bundle install codex --from artifact/codex +``` + +把 `cursor` 加入 `targets` 就会构建出 `artifact/cursor`,然后用 `npx agent-bundle install cursor --from +artifact/cursor` 以同样方式安装。`plugin` 构建则只有一份覆盖所有宿主的 `INSTALL.md`。 + +若想在 Claude Code 上进行免安装的开发循环: + +```sh +claude --plugin-dir artifact/claude plugin list --json +``` + +## 下一步 + +- [项目结构](./project-structure.mdx) —— 每个 `src/` 根目录的含义,以及输出落在哪里。 +- [配置模型](../authoring/index.mdx) —— 每个配置字段及其归属。 +- [Skills](../authoring/skills.mdx) —— 大多数插件最先编写的表面。 diff --git a/website/docs/zh/index.mdx b/website/docs/zh/index.mdx new file mode 100644 index 000000000..f49fc66a9 --- /dev/null +++ b/website/docs/zh/index.mdx @@ -0,0 +1,230 @@ +--- +pageType: home +description: '用一份带类型的配置描述 Skill、钩子、MCP 服务器与脚本,编译为可直接安装到 Claude Code、Codex 与 Cursor 的产物。' +titleSuffix: ' - 面向 Claude Code、Codex 与 Cursor 的 Agent 插件编译器' + +hero: + name: agent-bundle + text: 一份带类型的配置,适配所有 Agent 宿主 + tagline: 一次性描述 Skill、钩子、MCP 服务器与脚本,编译出可直接安装到 Claude Code、Codex 与 Cursor 的产物。 + image: + src: /logo.svg + alt: agent-bundle 徽标 + actions: + - theme: brand + text: 介绍 + link: /zh/guide/start/ + - theme: alt + text: 快速开始 + link: /zh/guide/start/quick-start + +features: + - icon: 🧩 + title: 一份带类型的配置 + details: 单个 agent-bundle.config.ts 承载项目身份、target 选择与策略。配置没说的部分由 src/ 约定补齐;两者描述同一件事时,配置始终优先。 + link: /zh/guide/authoring/ + span: 4 + - icon: 📚 + title: Skill + details: 每个 Skill 一个目录,包含 SKILL.md 与自己的资源;无需声明即可被发现,并按固定版本的 Agent Skills 规范校验。 + link: /zh/guide/authoring/skills + span: 4 + - icon: 🪝 + title: 生命周期钩子 + details: 从 sessionStart 到 workspaceOpen 共七种事件,用 TypeScript 写一次,编译成每个宿主实际拉起的包装脚本。 + link: /zh/guide/authoring/hooks + span: 4 + - icon: 🔌 + title: MCP 服务器与 MCP App + details: 手写的 stdio 服务器,或每个 tool、resource、prompt 各占一个模块的生成式服务器。浏览器端 MCP App 编译为自包含的 HTML。 + link: /zh/guide/authoring/mcp + span: 4 + - icon: 📜 + title: 脚本、静态资源与包入口 + details: 普通脚本或渲染式脚本、逐字节复制的静态资源,以及从同一项目输出的 CLI bin 或库入口——一次构建,两份输出。 + link: /zh/guide/authoring/scripts-assets + span: 4 + - icon: 🖥️ + title: 本地 Workbench + details: agent-bundle dev 在回环地址上提供开发者 Workbench:诊断、产物树、带原始协议轨迹的 MCP playground,以及钩子 playground。 + link: /zh/guide/development/workbench + span: 4 + - icon: 🔬 + title: 以证据驱动的测试 + details: 路由、协议、CLI、打包与宿主安装等各级证明,把“能构建”变成可复核的证据;某一级别的通过绝不会被当作另一级别的收据来报告。 + link: /zh/guide/development/testing + span: 4 + - icon: 📊 + title: 评估 + details: 评估套件具有通过、失败与不确定三种语义,针对真实输出的产物运行,而不是它的模拟品。 + link: /zh/guide/development/evaluations + span: 4 + - icon: 📦 + title: 每个 target 都能独立交付 + details: 已构建的 target 目录就是你安装的那个单位——它自带宿主清单与生成的 INSTALL.md。旁边的产物根目录保存着 agent-bundle.manifest.json,即校验、MCP、钩子与评测所读取的 SHA-256 记录。 + link: /zh/guide/distribution/ + span: 4 +--- + +import { Steps, Tab, Tabs } from '@rspress/core/theme'; + +## 你写下什么,得到什么 + +输入是一份配置文件加一棵按约定组织的 `src/` 目录树。输出是每个宿主各一个可直接安装的目录, +各自带有自己的宿主清单、生成的包装脚本,以及用捆绑包真实名称写成的安装说明——它们共同位于一个产物根目录之下, +根目录还保存着整个产物据以校验的 `agent-bundle.manifest.json`。 + +
+
+ +### 你写下 + +```ts title="agent-bundle.config.ts" +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { + name: 'release-tools', + description: 'Release-readiness checks.', + }, + hooks: { + sessionStart: { + handler: './src/hooks/session-start.ts', + }, + }, + targets: ['claude', 'codex', 'portable'], +}); +``` + +```text title="src/" +src/ +├── skills/release-review/ +│ ├── SKILL.md +│ └── references/policy.md +├── hooks/session-start.ts +├── mcp/status.ts +└── scripts/check-service.ts +``` + +Skill、MCP 服务器与脚本都按约定被发现。只有钩子需要声明,因为处理器必须绑定到某个事件。 + +
+
+ +### 编译器输出 + + + + +```text title="artifact/claude/" +artifact/claude/ +├── .claude-plugin/ +│ ├── plugin.json +│ └── marketplace.json +├── .mcp.json +├── hooks/ +│ ├── hooks.json +│ └── session-start-….mjs +├── mcp/mcp-status-….mjs +├── scripts/check-service.mjs +├── skills/release-review/ +│ ├── SKILL.md +│ └── references/policy.md +└── INSTALL.md +``` + + + + +```text title="artifact/codex/" +artifact/codex/ +├── .codex-plugin/plugin.json +├── .agents/plugins/marketplace.json +├── .mcp.json +├── hooks/ +│ ├── hooks.json +│ └── session-start-….mjs +├── mcp/mcp-status-….mjs +├── scripts/check-service.mjs +├── skills/release-review/ +│ ├── SKILL.md +│ └── references/policy.md +└── INSTALL.md +``` + + + + +```text title="artifact/portable/" +artifact/portable/ +├── plugin.json +├── mcp.json +├── mcp/mcp-status-….mjs +├── scripts/check-service.mjs +├── skills/release-review/ +│ ├── SKILL.md +│ └── references/policy.md +├── install.mjs +└── INSTALL.md +``` + +Agent Plugins 开放标准没有钩子这一表面。上面的钩子没有声明 `targets`,因此只继承所选 target 中支持钩子的那些,`portable` +在这里会被跳过且不产生诊断。若显式写出 `targets: ['portable']`,构建时则会得到 `AB4204`。 + + + + +生成的包装脚本文件名以一段短摘要结尾,摘要来自编译它的声明,而非文件内容。`artifact/agent-bundle.manifest.json` +记录每个输出文件及其 SHA-256,因此后续校验比对的是真实字节,而不是检查某个路径是否存在。 + +
+
+ +## 从源码到已安装的插件 + + + +### 描述 + +编写 `agent-bundle.config.ts`,把 Skill、钩子、MCP 路由与脚本放到 `src/` 下。 +`agent-bundle inspect` 会展示归一化后的模型,让你确认哪些文件是按约定被发现的、哪些是由配置声明的。 + +### 开发 + +`agent-bundle dev` 在每次变更时重新构建,并在回环地址上提供开发者 +[Workbench](/zh/guide/development/workbench):诊断、Skill 文档、带来源信息的产物树,以及驱动输出的 +MCP 服务器与钩子包装脚本的 playground。 + +### 证明 + +用彼此区分的[证明级别](/zh/guide/development/testing)针对产物做测试——从路由单元测试一直到通过真实宿主 +CLI 安装的捆绑包——并运行结果为通过、失败或不确定的[评估](/zh/guide/development/evaluations)。 + +### 交付 + +`agent-bundle build` 校验项目并为每个 target 写出一个目录。[校验](/zh/guide/distribution/validation) +依据清单检查产物,[安装](/zh/guide/distribution/installation)则走每个宿主自己的安装路径。 + + + +## 一份源码,所有宿主 + +| Target | 输出内容 | 安装方式 | +| --- | --- | --- | +| `claude` | Claude Code 插件布局,含插件清单与本地 marketplace 清单。 | `claude plugin marketplace add` 与 `claude plugin install`,或 `agent-bundle install claude`。 | +| `codex` | Codex 插件布局,含插件清单与本地 marketplace 清单。 | `codex plugin marketplace add` 与 `codex plugin add`,或 `agent-bundle install codex`。 | +| `cursor` | Cursor 插件布局。 | 生成的 `install.mjs`,或 `agent-bundle install cursor`。 | +| `portable` | [Agent Plugins](https://agent-plugins.org) 开放标准——Skill 与 MCP 服务器——Cursor、Codex、VS Code、GitHub Copilot、Kiro 与 ChatGPT 原生读取。 | 生成的 `install.mjs`。 | +| `plugin` | 一个多宿主捆绑包,在共享的组件目录之上同时携带 Claude、Codex 与 Cursor 清单。 | `install.mjs` 或任一宿主 CLI。 | + +各宿主能加载的内容不同,编译器会在构建时明确指出:你为某个 target 选择了它无法表达的表面,就会得到一条 +被报告的诊断,绝不会被悄悄省略。唯一有意为之的例外是没有自己的 `targets` 的钩子:它只继承支持钩子的 +宿主——正如上面的 `portable` 标签页所示——而不是让构建失败。每条诊断都有稳定的 `AB` 代码,记录在 +[诊断参考](/zh/reference/diagnostics)中。 + +## 从这里开始 + +- [安装](/zh/guide/start/installation)——运行要求与 `create-agent-bundle` 脚手架。 +- [快速开始](/zh/guide/start/quick-start)——从配置到已安装插件的完整项目。 +- [示例](/zh/examples/)——可运行的产品:从 Skill 起步项目、钩子与脚本轨迹、交互式 MCP App 到一个完整的媒体管理插件,外加两个进阶组合参考。 +- [Type API](/zh/api/)——为每个公开 `agent-bundle` 导出生成的参考。 diff --git a/website/docs/zh/reference/_meta.json b/website/docs/zh/reference/_meta.json new file mode 100644 index 000000000..714e233ef --- /dev/null +++ b/website/docs/zh/reference/_meta.json @@ -0,0 +1,14 @@ +[ + "index", + "cli", + "configuration", + "targets-artifacts", + "hosts", + "events", + "notices", + "diagnostics", + "runtime-environment", + "security", + "limitations", + "api" +] diff --git a/website/docs/zh/reference/api.mdx b/website/docs/zh/reference/api.mdx new file mode 100644 index 000000000..4990b1786 --- /dev/null +++ b/website/docs/zh/reference/api.mdx @@ -0,0 +1,43 @@ +--- +description: '生成的 agent-bundle 类型 API:它覆盖哪些入口点、如何产生,以及为什么生成式符号文档与语言无关。' +--- + +# 类型 API + +符号级参考是**从源码生成的**,不是手写的。请在[类型 API](/zh/api/)中浏览,或从顶部导航进入。 + +## 它覆盖什么 + +每个公开入口点都由其声明生成文档: + +| 入口点 | 内容 | +| --- | --- | +| `agent-bundle` | 编写与编排表面:`defineSkill`、`canonicalAgentEvents`、`startDevServer`、`runEvals`、`compareEvals`、eval harness 工厂,以及产物清单辅助函数。 | +| `agent-bundle/api` | 程序化编译器:`build`、`validate`、`inspect`、`prepack`,及其选项与结果类型,以及 `AgentComponentKind` / `componentKindCapability` 组件类型辅助。 | +| `agent-bundle/config` | `defineConfig` 与配置类型。 | +| `agent-bundle/test` | 路由测试 harness、匹配器与契约矩阵。 | +| `agent-bundle/test/browser` | 面向浏览器渲染视图的 MCP App bridge harness。 | +| `agent-bundle/eval` | eval 套件定义、断言与运行对比。 | +| `agent-bundle/rstest` | Rstest 配置助手。 | +| `agent-bundle/meta` | 构建期项目标识常量,编译器会在每个被编译的表面中替换它。 | +| `agent-bundle/mcp-apps` | MCP App 资源注册表契约,编译器会在本地 MCP 服务器中替换它。 | +| `agent-bundle/cli-entry` | 每个生成的 CLI 可执行文件所基于的路由式 CLI 外壳。 | +| `agent-bundle/mcp-entry` | 每个生成的 MCP 服务器所包裹的 stdio MCP 入口外壳。 | +| `agent-bundle/routes` | 路由模块的编写类型(`AgentEventRouteProps`、`ToolRouteProps`、`CliRouteProps` 与各类 `config` 形状),以及 `appResourceUri`——对同级 MCP App 的静态引用,编译器会把它解析为该 App 的 `resourceUri`。 | + +因为它是生成的,所以它始终与已交付的类型一致:签名、联合类型、默认值,以及各表面可能抛出的错误类。 +当本参考章节与生成页面不一致时,以生成页面为准。 + +## 生成页面与语言无关 + +站点在两个语言下都镜像了这份生成式参考,因此[/api/](/api/)与[/zh/api/](/zh/api/)对两种语言的读者都能 +解析。但其**内容**未被翻译:符号文档提取自源码注释,并保持在其被编写时所用的语言。 + +这是刻意的。生成输出的翻译副本会在第一次重构时就与源码脱节,而一份类型是最新的、散文却已过期的符号参考, +比一份诚实地只用一种语言的参考更糟。手写页面——指南以及本参考的其余部分——则是完整翻译。 + +## 从哪里开始 + +- 从脚本中编译:`agent-bundle/api` 中的 `build` 与 `validate`。 +- 编写测试:`agent-bundle/test` 中的 `renderRoute`、`expectDocument` 与那些矩阵,在[测试](../guide/development/testing.mdx)中介绍。 +- 编写 eval:`agent-bundle/eval` 中的 `defineEvalSuite` 与那些断言,在[评测](../guide/development/evaluations.mdx)中介绍。 diff --git a/website/docs/zh/reference/cli.mdx b/website/docs/zh/reference/cli.mdx new file mode 100644 index 000000000..c74eb7658 --- /dev/null +++ b/website/docs/zh/reference/cli.mdx @@ -0,0 +1,180 @@ +--- +description: '完整的 agent-bundle 命令行表面:dev、build、prepack、install、doctor、validate、eval、inspect、mcp 与 hooks,及其全部选项与退出码。' +--- + +# 命令行 + +```sh +npx agent-bundle --help +npx agent-bundle --version +``` + +## 命令 + +| 命令 | 用途 | +| --- | --- | +| `agent-bundle dev` | 在 loopback 上提供打包好的开发 workbench。 | +| `agent-bundle dev proxy` | 把宿主的 stdio MCP 流量桥接到正在运行的开发服务器。 | +| `agent-bundle build` | 构建一份已校验的 Agent Bundle 产物。 | +| `agent-bundle prepack` | 构建并校验 npm pack 清单。 | +| `agent-bundle install ` | 把已构建的捆绑包安装进受支持的宿主。 | +| `agent-bundle doctor` | 在不做任何改动的前提下检查宿主安装与运行时端点。 | +| `agent-bundle validate` | 校验项目源码或某一份产物。 | +| `agent-bundle eval` | 针对已构建产物运行确定性或原生 eval 套件。 | +| `agent-bundle eval compare` | 对比两次已持久化的 eval 运行。 | +| `agent-bundle inspect` | 检查规范化后的 target 与适配器计划。 | +| `agent-bundle mcp list` / `invoke` / `run` | 从产物中操作某个 MCP 服务器。 | +| `agent-bundle hooks list` / `simulate` | 检查并模拟生成的钩子。 | + +## 共享选项组 + +**源码命令** —— `build`、`prepack`、`validate`、`eval`: + +| 选项 | 默认值 | 含义 | +| --- | --- | --- | +| `--root ` | `process.cwd()` | 项目根目录。 | +| `--config ` | —— | 相对 `--root` 的配置文件。 | +| `--mode ` | `production` | 配置模式。 | +| `--target ` | 全部已配置 | 要选择的 target。可重复。 | +| `--json` | 关闭 | 写出一份机器可读的 JSON 文档。 | + +**产物命令** —— `mcp list`、`mcp invoke`、`mcp run`、`hooks list`、`hooks simulate`:同样的 +`--root`、`--config`、`--mode` 与 `--json`,外加 `--artifact `(精确使用某一份已构建产物)与 +`--target `(选择产物 target)。对 `mcp list`、`mcp invoke`、`mcp run` 与 `hooks simulate` +来说,`--target` 是**必填**的。 + +## dev + +| 选项 | 默认值 | 含义 | +| --- | --- | --- | +| `--root ` | `process.cwd()` | 项目根目录。 | +| `--port ` | 临时端口 | Loopback TCP 端口。 | +| `--agent-api` / `--no-agent-api` | 配置中的 `dev.agentApi` | 启用或禁用 `/mcp` 上经过认证的 Agent API。 | +| `--install-host ` | 无 | 安装并重新同步一个开发期宿主。可重复;取值为 `claude`、`codex` 或 `cursor`。 | +| `--open` / `--no-open` | `--no-open` | 前台服务器启动后是否打开 workbench。 | + +`dev` 在前台运行,并在收到终止信号时关闭会话。见 +[开发者 Workbench](../guide/development/workbench.mdx)。 + +### dev proxy + +| 选项 | 默认值 | 含义 | +| --- | --- | --- | +| `--server ` | **必填** | 生成的 MCP 服务器名。 | +| `--target ` | `portable` | 包含该 MCP 服务器的生成式 target。 | +| `--url ` | 自动发现 | 显式指定 loopback 开发服务器 origin。 | + +proxy 从父命令 `dev` 继承 `--root`,并把诊断写到 stderr。 + +## build 与 prepack + +两者都接受源码选项组,外加: + +| 选项 | 默认值 | 含义 | +| --- | --- | --- | +| `--output ` | 配置中的 `output.distPath`,否则为 `artifact` | 相对 `--root` 的产物输出路径。`dist` 属于包构建。 | + +当项目声明了 `bin` 或 `lib` 时,两者都会构建包输出。 + +## install + +```sh +agent-bundle install [--from ] [--scope ] [--replace] [--json] +``` + +| 选项 | 默认值 | 含义 | +| --- | --- | --- | +| `` | **必填** | `claude`、`codex` 或 `cursor`。 | +| `--from ` | `process.cwd()` | target 捆绑包目录或产物根目录。 | +| `--scope ` | `user` | `user`、`project` 或 `local`。Claude 接受全部三种;Codex 与 Cursor 是 user 作用域。 | +| `--replace`(别名 `--force`) | 关闭 | 即使版本不同,也替换该插件已有的 agent-bundle 安装。不带它时,内容完全相同的副本是 `already-installed` 空操作,版本相同但内容哈希不同的副本会被自动替换,版本不同则为 `AB7005`。外来目录总是被拒绝(`AB7005`)。 | + +输出的独立 `install.mjs` 与相对包的安装器 bin 接受同样的 `--replace`。Cursor 副本携带安装回执 +(`.agent-bundle-install.json`),替换只触碰归属文件,`--replace` 会接管回执出现之前的副本;Claude 的替换 +先运行 `claude plugin uninstall --keep-data` 再重新安装,Codex 则先 `codex plugin remove` 再 `add`。 + +## doctor + +| 选项 | 默认值 | 含义 | +| --- | --- | --- | +| `--host ` | 全部受支持的宿主 | 要检查的宿主。可重复;取值为 `claude`、`codex` 或 `cursor`。 | +| `--from ` | —— | 用于比对的 target 捆绑包目录或产物根目录。 | +| `--json` | 关闭 | 写出一份机器可读的 JSON 文档。 | + +当 Doctor 报告任何 error 级诊断时以 `1` 退出,并且绝不修改任何东西。带 `--from` 时,它按宿主把已安装副本报告为 +`current`、`stale`(版本相同、内容不同——`AB7308`)、`version-mismatch`(`AB7309`)、`foreign`(`AB7321`)或 +`not-installed`(`AB7307`)。 + +## validate + +| 选项 | 默认值 | 含义 | +| --- | --- | --- | +| `--artifact ` | —— | 精确校验这份已构建产物,而不是项目源码。 | +| `--host-validation` / `--no-host-validation` | 开启 | 为兼容的已构建 target 运行已安装的宿主开发者工具。 | +| `--strict` | 关闭 | 把宿主工具的 warning 提升为 error。 | + +## eval + +| 选项 | 默认值 | 含义 | +| --- | --- | --- | +| `--artifact ` | 从源码构建 | 精确评测这份已构建产物。 | +| `--case ` | 全部 | 要运行的 eval case id。可重复。 | +| `--suite ` | 全部 | 要运行的 eval 套件名。可重复。 | +| `--harness ` | `deterministic` | `deterministic`、`claude` 或 `codex`。 | +| `--trials ` | 套件声明值 | 为每个所选 case 运行这么多次试验。正整数,最大 `100`。 | + +`eval compare ` 接受两个已持久化的运行 id,并从 `eval` 继承源码选项。 + +## inspect + +`inspect` 接受 `--root`、`--config`、`--mode`、`--json` 与单个 `--target ` 过滤器 +(不可重复),外加**至多一个**聚焦标志: + +| 标志 | 聚焦于 | +| --- | --- | +| `--bundler` | 合成出来的打包器配置。 | +| `--hooks` | 钩子。 | +| `--routes` | 编译后的路由图。 | +| `--skills` | Skill。 | +| `--state` | 状态生命期。 | + +传入多个聚焦标志是错误。当规范化模型无效时,`inspect` 以 `1` 退出。 + +每份检查计划都会以规范组件类型报告每个组件——`agent`、`cli`、`command`、`event-route`、`hook`、`lsp`、 +`mcp-app`、`mcp-server`、`native-diagnostics`、`native-extension`、`rule`、`script`、`skill`(来自 +`agent-bundle/api` 的 `AgentComponentKind`)——标为 `selected` 或带原因的 `skipped`,并附一份 `kinds` 矩阵: +每种类型一条,包含该 target 自己的四态能力判定以及选中/省略计数。人类可读输出为每个 target 打印一行、每条 +省略及其原因,然后是 `kinds this host cannot emit:`。文件系统 `src/events` 路由报告为 `event-route`,按宿主的 +`event:<规范事件>` 行判定,而不是 `hook`。 + +## mcp + +`mcp list` 需要 `--server `。`mcp invoke` 需要 `--server` 与 `--tool `,并且只接受 +`--input ` 与 `--input-file ` 二者之一;解析出的值必须是 JSON 对象,而不是数组或标量。 + +`mcp run` 需要 `--server`,并以继承的 stdio 在前台运行一个 stdio 服务器: + +| 选项 | 默认值 | 含义 | +| --- | --- | --- | +| `--env-file ` | 约定集合 | 精确加载这个 `.env` 文件,替换项目根目录的那一组。可重复。 | +| `--no-env` | 关闭 | 不加载任何 `.env` 文件即启动。与 `--env-file` 互斥。 | +| `--plugin-root ` | 项目根目录 | 让 env 中的 plugin-root 锚点相对这个根展开。 | + +由于在整个前台运行期间由服务器持有 JSON-RPC 通道,`mcp run` 自身不向 stdout 写任何东西。`.env` 优先级 +规则,以及 plugin-root 锚点改变了什么,见[运行时环境](./runtime-environment.mdx)。 + +## hooks + +`hooks list` 报告某份产物中的钩子。`hooks simulate` 需要 `--hook `(id 或名字),以及与 +`mcp invoke` 相同的 `--input` / `--input-file` 二选一。 + +## 退出码 + +| 退出码 | 含义 | +| --- | --- | +| `0` | 成功,包括 `--help` 与 `--version`。 | +| `1` | 一次被报告的失败:一条 error 级诊断、`inspect` 得到的无效模型、失败或结论不明的 eval 运行,或者被作为一条 `AB5000` 诊断写到 stderr 的未捕获错误。 | +| `2` | 来自命令行解析器的参数解析失败。 | + +当任何一次试验**失败或结论不明**时,eval 运行以 `1` 退出——结论不明的试验没有产生证据,因此它同样不能 +报告成功。 diff --git a/website/docs/zh/reference/configuration.mdx b/website/docs/zh/reference/configuration.mdx new file mode 100644 index 000000000..f9ab8dc87 --- /dev/null +++ b/website/docs/zh/reference/configuration.mdx @@ -0,0 +1,152 @@ +--- +description: 'agent-bundle.config.ts 字段参考:每个顶层键、可接受的形态、默认值与校验规则,包括已废弃的 plugin.version 字段。' +--- + +# 配置 + +概念模型——配置拥有什么、`src/` 约定拥有什么——在[配置模型](../guide/authoring/index.mdx)中。本页是 +字段契约。 + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + plugin: { description: 'Evidence-backed project tools.', name: 'my-plugin' }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +## 顶层字段 + +| 字段 | 类型 | 默认值 | +| --- | --- | --- | +| `plugin` | `{ name, description?, logo?, ... }` | **必填。** | +| `targets` | `string[]` | 由适配器选择。 | +| `skills` | `string[]` | `src/skills/*` 约定。 | +| `hooks` | `Partial>` | `src/hooks/*` 约定。 | +| `mcp` | `{ servers: Record }` | `src/mcp/*` 约定。 | +| `scripts` | `Record` | `src/scripts/*` 约定。 | +| `assets` | `string[]` | 根目录 `assets/` 约定。 | +| `bin` | `false \| Record` | `src/cli.ts` 约定。 | +| `lib` | `false \| string \| { entry, dts? }` | `src/index.ts` 约定。 | +| `routes` | 路由图策略 | 由约定推导。 | +| `output` | `{ distPath? }` | 命令行下为 `artifact`;不带 `packageOutputs` 的 `build()` 下为 `dist`。 | +| `runtime` | `{ node }` | Node 22.12。 | +| `payload` | `Record` | 无。 | +| `state` | `false` | `src/state.ts` 约定。 | +| `marketplace` | `boolean` | 由适配器选择。 | +| `dev` | `{ agentApi?, contracts?, runtime? }` | 无。 | +| `evals` | `{ include?, runsDir?, semanticGrader? }` | 见下文。 | +| `tools` | `{ rsbuild?, rspack? }` | 无。 | + +宿主作用域的扩展键(`claude`、`codex`、`cursor`、`portable`)由各 target 适配器通过对 +`AgentBundleConfigExtensions` 的声明合并贡献。扩展值必须是严格的有限 JSON(`AB4500`),且宿主特定的值 +留在各自的适配器中,而不进入编译器核心。每个适配器所拥有的键的逐字段清单见 +[宿主作用域声明](../guide/authoring/index.mdx#宿主作用域声明)。 + +### 生成的类型定义 + +配置是一份 TypeScript 契约,而不是运行时 schema:`defineConfig` 接受一个 `AgentBundleConfig`(或返回 +它的工厂函数),`validateSource` 则用结构化诊断强制执行本页的规则。下面这些精确形态在每次文档构建时由 +TypeDoc 从包源码生成,因此不会与已发布的类型产生偏差。 + +| 字段 | 类型定义 | +| --- | --- | +| 整个配置 | [`AgentBundleConfig`](../api/types/index.AgentBundleConfig.md) · [`defineConfig`](../api/functions/index.defineConfig.md) · [`ConfigFactory`](../api/types/index.ConfigFactory.md) · [`ConfigFactoryContext`](../api/interfaces/index.ConfigFactoryContext.md) | +| `mcp` | [`AgentBundleMcpConfig`](../api/interfaces/index.AgentBundleMcpConfig.md) · [`AgentBundleMcpServer`](../api/interfaces/index.AgentBundleMcpServer.md) · [`AgentBundleMcpApp`](../api/interfaces/index.AgentBundleMcpApp.md) · [`McpTransport`](../api/types/index.McpTransport.md) | +| `payload` | [`AgentBundlePayloadConfig`](../api/types/index.AgentBundlePayloadConfig.md) · [`AgentBundlePayloadEntry`](../api/interfaces/index.AgentBundlePayloadEntry.md) · [`AgentBundlePrebuiltEntry`](../api/interfaces/index.AgentBundlePrebuiltEntry.md) | +| `output` | [`AgentBundleOutputConfig`](../api/interfaces/config.AgentBundleOutputConfig.md) | +| `runtime` | [`AgentBundleRuntimeConfig`](../api/interfaces/index.AgentBundleRuntimeConfig.md) | +| `dev` | [`AgentBundleDevConfig`](../api/interfaces/index.AgentBundleDevConfig.md) · [`AgentBundleDevContractsConfig`](../api/interfaces/index.AgentBundleDevContractsConfig.md) · [`AgentBundleDevRuntimeConfig`](../api/interfaces/api.AgentBundleDevRuntimeConfig.md) | +| 宿主扩展 | [`AgentBundleConfigExtensions`](../api/interfaces/index.AgentBundleConfigExtensions.md) · [`AgentBundlePortableConfig`](../api/interfaces/config.AgentBundlePortableConfig.md) | +| 校验 | [`validateSource`](../api/functions/config.validateSource.md) · [`validateModel`](../api/functions/config.validateModel.md) · [`loadConfig`](../api/functions/config.loadConfig.md) | + +## plugin + +| 字段 | 规则 | +| --- | --- | +| `name` | 必填。宿主原生的插件 slug,绝不是 npm 包名。 | +| `description` | 一句话,会写进生成的清单中。 | +| `logo` | 项目相对的图片路径。缺失、不是文件或位于项目之外报告 `AB4012`;在已构建产物中缺失或逃逸出部署树报告 `AB6025`。 | +| `version` | **已废弃。** | + +`package.json` 对发布标识具有权威性。发布版本只在那里声明:与 `package.json` 不一致的 `plugin.version` +会报告 `AB4008` 警告,而任何地方都没有版本的发布构建会被 `AB4013` 拒绝,而不是交付 +`0.0.0-dev.` 这个开发期回退值。该字段仅为兼容而保留,并将按照正常的破坏性变更策略移除。 + +## hooks + +键是七个标准事件:`sessionStart`、`beforeTool`、`afterTool`、`stop`、`agentStart`、`agentStop`、 +`workspaceOpen`。值是一个条目或条目数组,每个条目要么是模块路径,要么是 +`{ handler, tools?, targets?, timeout?, args? }`。每个规范事件与工具选择器如何降级为宿主原生事件与 +匹配器,见生成的[事件与钩子矩阵](./events.md)。 + +**钩子结果**契约完整地记录在[钩子](../guide/authoring/hooks.mdx)中。有一个字段值得在此重复,因为它很 +容易搞错:`reason` 是一个非空字符串,**仅**在拒绝 `beforeTool`、`stop` 或 `agentStop` 钩子时有效,而拒绝 +其中之一却不给出 reason 会失败。 + +## output 与 runtime + +`output.distPath` 是相对项目根目录的产物输出目录。命令行(`build`、`prepack`、`dev`)把它默认为 `artifact`, +因为命令行同时运行包构建,而包构建拥有 `dist/`;编程式 `build()` 除非传入 `packageOutputs: true`,否则默认为 +`dist`。逐次调用的 `--output` 标志优先,但同样 +受相同的项目根包含性检查约束;绝对或外部输出路径、形态无效的路径,或落入保留编译器命名空间的路径 +均不受支持(`AB4707`–`AB4709`)。 + +`runtime.node` 是 `major.minor[.patch]` 形式的最低 Node.js 版本。它只能抬高生成式可执行文件的默认下限, +绝不能降低,且所选下限会作为 `runtime.node` 记录在产物清单中。这个下限本身在 +[配置模型](../guide/authoring/index.mdx)中介绍。 + +## payload + +键是产物根目录下的目标目录——一个位于编译器自有命名空间之外的安全单路径段——值是已经构建好的源目录。 +payload 树按字节逐一复制,并且对编译器而言是**不透明的**:编译器无法改写其内部的同级引用,因此稳定的 +名字就是正确性契约。完整性仍然通过产物清单保持内容寻址。 + +## evals + +只接受三个键;其他一律拒绝。 + +| 字段 | 默认值 | 规则 | +| --- | --- | --- | +| `include` | `['evals/**/*.eval.ts']` | 非空的相对模式数组,且绝不逃逸出项目根目录。 | +| `runsDir` | `.agent-bundle/runs` | 项目根目录下的相对子目录。 | +| `semanticGrader` | 无 | 必须恰好包含 `harness` 与 `model`。`harness` 必须是 `'claude'`;`model` 必须是安全的模型标识符,而不是路径。 | + +`evals` 块中的提供方凭据材料会被直接拒绝(`EVAL_CREDENTIAL_REJECTED`)—— Agent Bundle 复用宿主 CLI 已有 +的已登录会话。见[安全](./security.mdx)。 + +```ts twoslash +import { defineConfig } from 'agent-bundle/config'; + +export default defineConfig({ + evals: { + include: ['evals/**/*.eval.ts'], + runsDir: '.agent-bundle/runs', + semanticGrader: { harness: 'claude', model: 'claude-sonnet-4-5' }, + }, + plugin: { description: 'Evidence-backed project tools.', name: 'my-plugin' }, + targets: ['portable', 'claude'], +}); +``` + +## dev + +仅用于开发、绝不会成为已构建产物一部分的设置。 + +| 字段 | 含义 | +| --- | --- | +| `dev.agentApi` | 从 `agent-bundle dev` 暴露那个经过认证、仅 loopback 的 Agent API。`--agent-api` / `--no-agent-api` 标志会覆盖它。 | +| `dev.contracts.fixtures` | **设置了 `dev.contracts` 时必填。** 项目相对路径的模块,其默认导出把路由 id 映射到契约夹具。声明 `dev.contracts` 会让 `agent-bundle dev` 从直接采用每个 epoch,改为以开发契约矩阵门控面向宿主的采用:检查失败的 epoch 仍会发布到 Workbench playground,但活跃的宿主连接与开发安装会保留最后一个通过的 epoch(`AB7211`)。块本身格式错误、夹具模块逃出项目根、无法加载或导出了错误的形状,则是 `AB7210`。 | +| `dev.contracts.server` | 矩阵要检查的 MCP 服务器。仅当项目恰好编译一个服务器时可省略。 | +| `dev.runtime.provider` | 开发期运行时 provider 模块。 | + +## tools + +唯一的打包器逃生舱。两个片段都会以「最后但有界」的方式合并进框架合成的每一份打包器配置,并且产物不变式 +断言仍会在合并之后运行,因此破坏产物契约的逃生舱取值是一条硬性诊断,而不是一次无声的覆盖(`AB472x`)。 + +逃生舱在两份打包器引擎副本下执行:产物脚本、MCP 入口、钩子与包构建通过 Rslib 内嵌的 Rsbuild/Rspack 编译, +而 MCP App 视图通过工作区固定的 `@rsbuild/core` 编译。绝不要基于导入的 `@rspack/core` 构造插件或执行 +`instanceof` 检查——请使用传给 `tools.rspack` 变更函数的 `utils` 参数 +(`(config, { rspack }) => ...`),它总会交给你当前执行引擎自己的 `rspack` 对象。 diff --git a/website/docs/zh/reference/index.mdx b/website/docs/zh/reference/index.mdx new file mode 100644 index 000000000..7f5628491 --- /dev/null +++ b/website/docs/zh/reference/index.mdx @@ -0,0 +1,49 @@ +--- +description: 'agent-bundle 参考资料:命令行表面、配置字段、target 产物、运行时环境、安全边界、已知限制,以及生成的类型 API。' +--- + +# 参考 + +指南讲的是如何构建并交付一份捆绑包。本章是查阅用的那一半:确切的标志、确切的字段名、确切的默认值, +以及框架拒绝跨越的那些边界。 + +这里不重复任何教程。凡是指南中已经解释过的概念——配置模型、七个钩子事件、证明级别——本章只链接过去, +并仅记录契约本身。 + +| 页面 | 回答什么 | +| --- | --- | +| [命令行](./cli.mdx) | 每条命令、参数、选项、默认值与退出码。 | +| [配置](./configuration.mdx) | `agent-bundle.config.ts` 的每个字段、类型与校验规则。 | +| [Target 与产物](./targets-artifacts.mdx) | 各 target 输出什么,以及产物清单契约。 | +| [宿主能力矩阵](./hosts.md) | 固定的各宿主能力表:版本、清单、安装方式、路径 token、MCP 传输、插件组件。构建时生成。 | +| [事件与钩子矩阵](./events.md) | 各宿主的规范事件到原生事件、工具选择器到原生匹配器、被推迟的原生事件。构建时生成。 | +| [通知投递矩阵](./notices.md) | 每个宿主支持哪些通知通道,其余通道为何不可用。构建时生成。 | +| [诊断参考](./diagnostics.md) | 每个 `AB` 代码族、触发条件、严重级别与恢复提示。构建时从仓库契约生成。 | +| [运行时环境](./runtime-environment.mdx) | Node 版本下限、路径 token、环境变量、`.env` 分层与持久状态位置。 | +| [安全](./security.mdx) | 凭据、网络与信任边界。 | +| [已知限制](./limitations.mdx) | 框架目前不做什么、不能证明什么。 | +| [类型 API](./api.mdx) | 全部公开导出的生成式符号参考。 | + +## 如何读诊断 + +每条命令报告的都是结构化诊断,而不是散文式错误。一条诊断包含稳定的 `AB` 代码、一个严重级别、一条消息, +通常还有 `sourcePath` 与一条 `recovery` 提示。对于由诊断把关的命令——`build`、`prepack`、`validate`、 +`doctor`、`install` 与 `dev`——只有 **error** 级别才会让命令以非零退出;warning 与 info +绝不会为构建、校验或 dev 重建把关。`eval` 与 `inspect` 还有一个与诊断无关的以 `1` 退出的理由: +失败或无定论的试验,或者无效的模型——见[命令行退出码](./cli.mdx#退出码)。 + +完整的代码目录见[诊断参考](./diagnostics.md),它在构建时由仓库中的 +[`docs/diagnostics.md`](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/diagnostics.md) +渲染而来。 + +| 家族 | 领域 | +| --- | --- | +| `AB30xx` | Skill 文档:Markdown 解析与渲染式 skill 编译。 | +| `AB40xx`–`AB47xx` | 插件元数据、规范化模型不变式、钩子、MCP、脚本、资源、包构建,以及打包器逃生舱。 | +| `AB48xx`–`AB49xx` | 路由图、state、布局与 provider 约定:路由模块发现、`config` 提取、共享布局(`AB4830`–`AB4832`),以及编译后的命令表面。 | +| `AB5000` | 通用的命令行与适配器失败。 | +| `AB60xx` | 已构建产物校验,包括宿主 schema 文档与被引用的文件。 | +| `AB700x`–`AB7013` | 宿主安装与 npm prepack 门禁。 | +| `AB7xxx` | 项目准备与开发期重建,其中 `AB7300`–`AB7320` 为只读 Doctor。 | +| `AB8xxx` | 开发服务器配置与 Workbench 路由。 | +| `AB9xxx` | eval 选择、harness 与持久化运行记录。 | diff --git a/website/docs/zh/reference/limitations.mdx b/website/docs/zh/reference/limitations.mdx new file mode 100644 index 000000000..0938a44de --- /dev/null +++ b/website/docs/zh/reference/limitations.mdx @@ -0,0 +1,54 @@ +--- +description: 'agent-bundle 的已知限制:未迁移的 pre-0.1 持久记录、仅 loopback 的开发、原生 harness 的前提条件、被推断的 Codex 激活证据,以及未经核实的对比 facet。' +--- + +# 已知限制 + +明确写出来,以免把缺口误当成缺陷。 + +## 持久记录 + +由 pre-0.1 构建——即无版本持久记录切换之前——写出的开发期快照与导出**不会被迁移**。升级之前请重新构建 +产物,并丢弃那些预览期的 Eval 与 Playground 记录。当前的读取器会拒绝这些被取代的形态,而不是去猜测 +兼容性。 + +## 开发服务器 + +Workbench 只绑定 loopback,并且是一个前台开发会话,而不是托管服务。 + +## 原生 harness + +原生 Claude 与 Codex harness 要求那些 CLI 已安装并已登录。缺失、不兼容或未认证的 CLI 会被报告为 +**harness 失败**,这与插件失败是两回事。它们的实时冒烟测试是可选启用的,不属于日常测试运行。 + +语义评分要求原生 Claude harness 与一个已登录的 Claude Code 会话;配置了它之后,确定性与 Codex 选择会被 +拒绝。 + +## 无法被观察到的证据 + +Codex 未暴露权威的 Skill 激活事件,因此 Codex 的激活证据是 `inferred`,绝不会被报告为 `observed`。 + +某次运行没有记录的对比 facet —— 语义 grader 身份、宿主 CLI 版本、invocation —— 会被标记为**未经核实**, +而不是被假定为已对齐。 + +## 契约矩阵不能证明什么 + +没有任何矩阵边界能证明浏览器 App 的 HTML,或产物重建后的重放。内存内运行,以及不含事件路由的已编译 +产物,都会诚实地把运行时身份报告为 `not-applicable`。 + +packed 边界无法加载项目路由模块——源码可能已被删除并被核实为不存在——因此序列化往返、compat 探测与版本 +偏移检查会带着明确理由被报告为 `not-applicable`,而不是被无声跳过。`host-install` 以同样方式报告依赖 +模块的检查,因为加载项目模块会重新跨回源码与构建树。 + +当被公布的输入 schema 声明了 `additionalProperties: false` 时,普通的 `z.object` 工具路由仍可能剥掉未知 +键而不产生协议失败。当其他生成的负面用例仍能证明拒绝路径时,负面输入检查会记录这一容忍。 + +## 宿主校验 + +Codex 与 Cursor 都没有发布插件校验的开发者动词,因此它们输出的文档是针对 vendored 的固定 schema 检查 +的,而宿主的 schema 生成器被视为漂移信号而不是替代契约。见 +[产物校验](../guide/distribution/validation.mdx)。 + +## 渲染 + +Skill Markdown 中的原始 HTML、JSX/MDX 与 Mermaid 在 Workbench 渲染器中是惰性的。 diff --git a/website/docs/zh/reference/runtime-environment.mdx b/website/docs/zh/reference/runtime-environment.mdx new file mode 100644 index 000000000..8cc68abd2 --- /dev/null +++ b/website/docs/zh/reference/runtime-environment.mdx @@ -0,0 +1,82 @@ +--- +description: '生成式 agent-bundle 可执行文件的运行时契约:Node.js 版本下限、宿主路径 token、AGENT_BUNDLE 环境变量、.env 优先级与持久状态位置。' +--- + +# 运行时环境 + +## Node.js + +编译器与 Workbench 需要 **Node.js 22.19 或更高**;除非 `runtime.node` 抬高下限,生成式可执行文件面向 +**Node.js 22.12 或更高**。两者都在[安装](../guide/start/installation.mdx)中说明,而所选的可执行文件下限 +会作为 `runtime.node` 记录在产物清单中。 + +## 宿主路径 token + +生成的文档通过各宿主会替换的那个 token 引用插件根目录,编译器按 target 写出正确的拼写,而不是假定只有 +一种: + +| 宿主 | 插件根 | 插件数据 | +| --- | --- | --- | +| Claude Code | `${CLAUDE_PLUGIN_ROOT}` | `${CLAUDE_PLUGIN_DATA}` | +| Cursor | `${CURSOR_PLUGIN_ROOT}` | — (拒绝:没有文档化的宿主等价物) | +| Codex | `${PLUGIN_ROOT}`(仅钩子命令) | — (拒绝:没有文档化的宿主等价物) | +| portable | `${PLUGIN_ROOT}` | `${PLUGIN_DATA}` | + +替换是**按位置的,而不是全局的**。Codex 的 `${PLUGIN_ROOT}` 只是其生成的钩子命令的命令根,别无他用: +Codex 的 MCP 运行时不插值任何路径 token,因此 MCP `command`、`args` 或 `env` 取值中的插件根 token +只有在位于取值开头、且该服务器的 `cwd` 就是插件根时才被接受,此时编译器会把它改写为 `cwd: "./"` +之下的 `./` 相对路径;嵌在中间的 token、缺少该 `cwd` 的 token,以及任何插件数据或工作区根 token +都是构建错误。Claude Code 只在 Skill 与 agent 内容、钩子与 monitor 命令、MCP 服务器 +以及 LSP 服务器中替换 `${CLAUDE_PLUGIN_ROOT}` 及其同类——绝不在 `settings.json` 中替换,这正是 +`claude.settings` 直接拒绝路径 token 的原因。Cursor 被固定的加载器有自己的可替换字段表,位于该表之外的 +token 会在构建时报告 `AB6028`,并由 Doctor 报告 `AB7320`。 + +## 环境变量 + +| 变量 | 由谁读取 | 含义 | +| --- | --- | --- | +| `AGENT_BUNDLE_PLUGIN_ROOT` | 生成式可执行文件 | 持久状态锚点。覆盖内置的回退值。 | +| `AGENT_BUNDLE_AGENT_API_TOKEN` | `agent-bundle dev` | Agent API 在启用之前所必需的 bearer token。 | +| `AGENT_BUNDLE_HOOK_HOST` | 生成的钩子 wrapper | 显式指定声明的宿主,而不去探测。 | +| `AGENT_BUNDLE_HOOK_SIMULATION` | 生成的钩子 wrapper | `1` 标记一次模拟调用;Workbench 的钩子 playground 会设置它。 | +| `AGENT_BUNDLE_NATIVE_HOST_CONTRACTS` | 贡献者测试套件 | `1` 用于比对已安装宿主 CLI 的契约。 | +| `AGENT_BUNDLE_NATIVE_CLAUDE_SMOKE` | 贡献者测试套件 | `1` 用于运行已登录的 Claude 原生冒烟测试。 | +| `AGENT_BUNDLE_NATIVE_CODEX_SMOKE` | 贡献者测试套件 | `1` 用于运行已登录的 Codex 原生冒烟测试。 | +| `AGENT_BUNDLE_WORKBENCH_API_PROXY` | 贡献者 HMR(`packages/workbench/scripts/dev.mjs`) | 启动 Workbench 界面 HMR 之前必须设置的、正在运行的 Agent Bundle 前台服务器 URL。见[开发者 Workbench](../guide/development/workbench.mdx)。 | + +这三个原生冒烟测试的开关之所以存在,是因为那些运行需要一个真实的、已登录的 CLI;它们绝不属于日常测试 +运行的一部分。 + +## `mcp run` 下的 .env 优先级 + +`agent-bundle mcp run` 按三层组合启动环境,优先级由低到高: + +1. **生成式服务器自身的 env**,其中路径 token 已经解析完毕。 +2. **`.env` 文件层**。 +3. 操作者真实的 `process.env`,因此已导出的变量总是胜出。 + +`.env` 层是项目根目录下(运行时称之为工作区根目录)Rsbuild 的约定集合 —— `.env`、`.env.local`、 +`.env.`、`.env..local` —— 除非 `--env-file ` 用你指定的那些文件替换它,或者 +`--no-env` 把它移除。 +这两个标志互斥。文件被读入一个临时对象,因此真实的 `process.env` 绝不会被改动。 + +## 持久状态 + +持久状态解析到 `$AGENT_BUNDLE_PLUGIN_ROOT/state`,回退到产物根目录,对 CLI bin 则回退到 +`./.agent-bundle/state`。只有 `workspace-durable` 状态定义使用 SQLite 驱动;其他生命期使用内存驱动, +不在磁盘上留下任何东西。 + +在 `mcp run` 之下,**env 取值**中的 plugin-root 锚点默认展开到项目根目录,而不是产物:在那里产物只是 +一个临时构建产物,把持久状态锚定其上会让状态在每次重建时被割裂。若要按字节忠实地演练一次「复制产物后 +启动」,请传入指向产物 target 根目录的 `--plugin-root `。 + +当服务器名是单个安全路径段时,逐服务器状态目录直接使用该名字;其他任何名字都会变成内容寻址的 +`server-` 段,因此像 `../shared` 这样的名字绝不可能穿出状态根目录。 + +默认的状态投影预算为:每次提交 5,000 毫秒、每个事件 262,144 字节、状态 1,048,576 字节、100,000 次修订。 +`agent-bundle inspect --state` 会为每个定义报告解析出的驱动、生命期、持久位置与预算来源。 + +## 下一步 + +- [安全](./security.mdx) —— 这些进程周围的凭据与网络边界。 +- [命令行](./cli.mdx) —— 上文引用的那些标志。 diff --git a/website/docs/zh/reference/security.mdx b/website/docs/zh/reference/security.mdx new file mode 100644 index 000000000..7038e1602 --- /dev/null +++ b/website/docs/zh/reference/security.mdx @@ -0,0 +1,71 @@ +--- +description: 'agent-bundle 的安全边界:不接受提供方凭据、仅 loopback 的开发表面、Agent API bearer token、安装器的克制,以及惰性的 Skill Markdown。' +--- + +# 安全 + +下面大多数内容是一种拒绝,而不是一项功能。它们是框架恪守的契约,不是你能用某个标志放宽的默认值。 + +## 提供方凭据 + +**本包绝不接受、索取、注入或持久化任何模型提供方 API key。** + +原生 Claude 与 Codex harness 使用已经安装并已登录的 CLI,并且是在提供方 key 相关的环境变量被移除*之后* +运行的。`evals` 配置块中形似凭据的材料会被直接拒绝而不是被忽略:Agent Bundle 复用宿主 CLI 已有的会话, +并且不存在任何从配置中读取 API key 的回退路径。 + +每次 Codex 试验都会设置一个临时的 `CODEX_HOME`,并只把已安装 CLI 那份不透明的 `auth.json` 复制进去。 +你日常的 Codex home、配置与已安装插件状态不会被用作试验状态,也不会被改动。贡献者冒烟命令会移除提供方 +API key 与形似凭据的环境取值,并在所选宿主的日常 home 状态发生变化时失败。 + +原生的已认证冒烟测试被排除在默认与日常本地测试运行之外,而运行它们的那个可信自托管 CI 工作流使用既有的 +订阅会话,且**不使用任何工作流 secret**。 + +## 开发服务器 + +`agent-bundle dev` **只绑定 loopback**,并且是一个前台开发会话,而不是托管服务。浏览器不是可信输入源: +它绝不提供命令、工作目录、原生模型或凭据,且由浏览器提供的原生模型与凭据会被拒绝。所有操作都只是可信 +本地操作。 + +Skill Markdown 中的原始 HTML、JSX/MDX 与 Mermaid 在 Workbench 渲染器中是**惰性的**。Skill 文档是用于 +展示的内容,而不是用于执行的标记。 + +## Agent API + +Agent API 默认关闭。启用时,它是挂载在同一个 loopback 前台服务器 `/mcp` 路径上、经过认证的 Streamable +HTTP MCP 端点,拥有恰好十三个固定且有序的工具。 + +- 若在没有 `AGENT_BUNDLE_AGENT_API_TOKEN` 的情况下启用该端点,启动会在**开始服务之前**失败。 +- 这个固定 token 只被读取一次,绝不会被记录日志、持久化或返回。 +- 必须使用标准的 `Authorization: Bearer` 认证。 +- 客户端可以省略 `Origin`;一旦提供,它必须与前台 URL 完全一致。 +- 当该端点被禁用时,它是**不存在的**,而不只是未授权。 + +操作细节见[开发者 Workbench](../guide/development/workbench.mdx)。 + +## 安装 + +安装一个包绝不会改动宿主的插件状态:**任何 npm 生命周期都不会执行安装**。独立的 `install.mjs` 会复制到 +`~/.cursor/plugins/local/` 且不覆盖冲突内容,对内容相同的情况是幂等的,会拒绝版本或内容冲突, +绝不调用 `sudo`,也绝不修改 `PATH`。 + +`agent-bundle doctor` 在构造上就是只读的。它只按目录条目与文件系统元数据清点持久状态——绝不打开数据 +库——并且绝不修复任何东西。 + +## 产物完整性 + +对于被分发出去的捆绑包,内容寻址就是信任锚。每个输出文件都在 `agent-bundle.manifest.json` 中携带一份 +SHA-256,因此被修改过的产物会校验失败,而不是被悄悄装上;当源码、已构建产物、已安装产物与运行中进程的 +版本不一致时,`host-install` 证明级别会以失败告终。 + +路径包含性在两个方向上都被强制:逃逸出部署树的声明式 logo(`AB6025`)、解析到 Cursor 捆绑包之外的符号 +链接(`AB6028`)、符号链接逃逸出 `~/.cursor/plugins/local` 的已安装本地插件(`AB7320`)、离开项目根目录 +的 eval `runsDir`,以及会穿出自身状态根目录的 MCP 服务器名,全都会被拒绝。 + +## 未作出的声明 + +本包不发布示例 RSC provider,也不发布宿主凭据。原生 Claude 与 Codex 评测 harness(`runClaudeTrial`、 +`runCodexEvalTrial`、`--harness claude|codex`)驱动的是你已经安装并登录的 CLI,自身不携带任何凭据。第三方声明——包括 vendored MCP Inspector 快照 +的许可证与 provenance——随已发布的包一同交付。 + +各证据表面**不能**证明什么,见[已知限制](./limitations.mdx)。 diff --git a/website/docs/zh/reference/targets-artifacts.mdx b/website/docs/zh/reference/targets-artifacts.mdx new file mode 100644 index 000000000..520970b5c --- /dev/null +++ b/website/docs/zh/reference/targets-artifacts.mdx @@ -0,0 +1,69 @@ +--- +description: '各 agent-bundle target 输出什么,以及产物清单契约:文件种类、摘要、源输入、target schema 固定值与已记录的校验状态。' +--- + +# Target 与产物 + +target 表格——各 target 输出哪种布局,以及 portable 标准为何省略规则、命令与钩子——在 +[配置模型](../guide/authoring/index.mdx)中。目录布局在 +[项目结构](../guide/start/project-structure.mdx)中。本页讲的是这些布局必须满足的产物契约。 + +## 每个 target 目录都可分发 + +已构建的 target 目录就是分发单位:构建之后没有打包步骤。每个目录都携带宿主要读取的组件、一份使用捆绑包 +真实插件名与市场名写成的生成式 `INSTALL.md`,以及该 target 所需的安装表面。 + +| Target | 市场清单 | 安装表面 | +| --- | --- | --- | +| `claude` | 本地市场清单。 | `claude plugin marketplace add` + `claude plugin install`。 | +| `codex` | 本地市场清单。 | `codex plugin marketplace add` + `codex plugin add`。 | +| `cursor` | `marketplace: true` 时生成本地市场清单(`.cursor-plugin/marketplace.json`)。 | `install.mjs`。 | +| `portable` | —— | `install.mjs`。 | +| `plugin` | Claude、Codex 与 Cursor 三份清单,外加一份生成的 `AGENTS.md`。 | `install.mjs` 与各宿主 CLI。 | + +当某个 target 缺少必需的安装表面文件时,产物校验会报错,因此捆绑包不可能在缺少其 `INSTALL.md` 所承诺的 +安装器的情况下发布。npm pack 清单会检查同样的路径(`AB7010`)。 + +## agent-bundle.manifest.json + +每份产物输出一份清单,它是之后每一项完整性检查的输入 —— `validate --artifact`、`prepack`、 +`doctor --from`,以及 packed 与 installed-host 证明级别。 + +| 字段 | 内容 | +| --- | --- | +| `producer` | `{ name: 'agent-bundle', version }`。 | +| `project` | `configPath`、`configDigest`、`modelDigest`、`revision`、经过校验的 `packageName` 与 `packageVersion`(未打包的开发项目中不存在),以及 `sourceInputs`。 | +| `files` | 每个输出文件:`path`、`bytes`、`sha256`、`kind`、可选的 `mode`,以及它所派生自的 `sourceInputs`。 | +| `runtime` | `{ node }` —— 所选的生成式可执行文件下限。 | +| `targets` | 每个 target 的:`name`、`adapterRevision`、`observedVersion`,以及它据以校验的固定 `schemas`。 | +| `agentSkills` | Agent Skills 规范固定值:`specification`、`sourceRevision`、`schemaSha256`。 | +| `validation` | `source`、`artifact` 与各 target 的已记录状态。 | + +`kind` 取值为 `bundle`、`copy`、`generated` 或 `prebuilt` —— 分别是打包入口、按字节复制、编译器生成的 +文档,或预构建 payload 文件。`sourceInputs` 记录每个输入路径及其自身的 SHA-256,并在相关时记录它是否 +可执行。 + +由于每个文件都携带摘要,校验比对的是**真实字节**,而不是检查路径是否存在,因此被手工改过的生成文件会 +失败。正是这一契约,让 `validate --artifact`、`mcp` 与 `hooks` 能在项目源码已被删除的产物上工作。 + +## 版本与修订 + +有四条版本轴被分别追踪,并且被期望彼此一致: + +- **源码** —— 项目 `package.json` 中的发布版本。 +- **已构建产物** —— 清单中的 `project.packageVersion`。 +- **已安装产物** —— 在宿主已安装根目录下找到的那份清单。 +- **运行中的进程** —— 活跃的 MCP `initialize` 所报告的版本。 + +`host-install` 证明级别会记录全部四者,并在任何一项缺失或不一致时以失败告终。`prepack` 则为前两者,外加 +规范化后的插件元数据与宿主清单把关(`AB7013`)。 + +target 行还记录 `adapterRevision`(单调递增,仓库自有)与 `observedVersion`(记录该能力证据时所观察到的 +宿主版本)。两者都不做哈希:Git 已经为仓库自有内容做了版本管理,在仓库内部再哈希一遍会让每次编辑表格 +都产生变动噪声。哈希固定只保留给 vendored 的外部内容 —— `src/adapters/schemas/*` 下带 `PROVENANCE.json` +的宿主文档 schema、Agent Skills schema 固定值,以及输出文件与源输入。 + +## 下一步 + +- [产物校验](../guide/distribution/validation.mdx) —— 读取这份清单的那些检查。 +- [运行时环境](./runtime-environment.mdx) —— 输出的可执行文件在运行时假定了什么。 diff --git a/website/package.json b/website/package.json new file mode 100644 index 000000000..b7a7c8604 --- /dev/null +++ b/website/package.json @@ -0,0 +1,33 @@ +{ + "name": "@agent-bundle/docs", + "version": "0.0.0", + "private": true, + "type": "module", + "engines": { + "node": ">=22.19.0" + }, + "scripts": { + "build": "rspress build", + "check": "pnpm typecheck && pnpm build", + "dev": "rspress dev", + "preview": "rspress preview", + "typecheck": "tsc --project tsconfig.json" + }, + "devDependencies": { + "@mdx-js/react": "3.1.1", + "@rspress/core": "2.0.21", + "@rspress/plugin-llms": "2.0.21", + "@rspress/plugin-sitemap": "2.0.21", + "@rspress/plugin-twoslash": "2.0.21", + "@rspress/plugin-typedoc": "2.0.21", + "@shikijs/transformers": "4.4.3", + "@types/node": "26.4.0", + "@types/react": "19.2.18", + "@types/react-dom": "19.2.5", + "agent-bundle": "workspace:*", + "react": "19.2.8", + "react-dom": "19.2.8", + "typescript": "6.0.3", + "zod": "4.5.4" + } +} diff --git a/website/plugins/generated-reference.ts b/website/plugins/generated-reference.ts new file mode 100644 index 000000000..3c650c48c --- /dev/null +++ b/website/plugins/generated-reference.ts @@ -0,0 +1,712 @@ +import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import type { RspressPlugin } from '@rspress/core'; + +/** + * Build-time reference pages rendered from repository sources of truth: + * + * - the pinned host capability tables under + * `packages/agent-bundle/src/adapters/capabilities/*.json` become the host, + * event-route, and notice-delivery matrices; + * - `docs/diagnostics.md` becomes the diagnostics code reference. + * + * The output is written into every locale root before route scanning, so the + * pages take part in sidebar metadata, dead-link checks, search, and the LLM + * artifacts exactly like authored pages, while never being committed. + */ + +type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }; +type JsonObject = { [key: string]: JsonValue }; + +interface CapabilityRow { + readonly state?: string; + readonly reason?: string; + readonly nativeEvent?: string; + readonly availability?: Readonly>; +} + +interface HostCapabilityTable { + readonly fileName: string; + readonly host: string; + readonly version: string; + readonly data: JsonObject; +} + +export interface GeneratedReferenceLocale { + /** Locale key, such as `en` or `zh`. */ + readonly lang: 'en' | 'zh'; + /** Docs-root-relative locale directory, such as `en` or `zh`. */ + readonly dir: string; +} + +export interface GeneratedReferenceOptions { + /** Absolute repository root; capability tables and `docs/diagnostics.md` resolve beneath it. */ + readonly repoRoot: string; + readonly locales: readonly GeneratedReferenceLocale[]; +} + +export const generatedReferencePages = ['hosts', 'events', 'notices', 'diagnostics'] as const; + +const capabilitiesDir = ['packages', 'agent-bundle', 'src', 'adapters', 'capabilities']; +const diagnosticsSource = ['docs', 'diagnostics.md']; +const repositoryUrl = 'https://github.com/ScriptedAlchemy/agent-bundle/blob/main'; + +const isObject = (value: JsonValue | undefined): value is JsonObject => + typeof value === 'object' && value !== null && !Array.isArray(value); + +const asObject = (value: JsonValue | undefined): JsonObject => (isObject(value) ? value : {}); + +const asString = (value: JsonValue | undefined): string | undefined => + typeof value === 'string' ? value : undefined; + +/** + * Which MCP fields accept path tokens, per host. Cursor and portable record + * this directly as `mcp.pathTokens`; Claude records the same fact as the + * `mcpStdio` and `mcpRemote` groups of its plugin path-substitution table, + * so the reference derives the row from there rather than rendering `—`. + */ +const mcpPathTokenFields = (host: JsonObject): JsonObject => { + const explicit = asObject(asObject(host.mcp).pathTokens); + if (Object.keys(explicit).length > 0) { + return explicit; + } + const substitution = asObject(asObject(asObject(host.plugin).packageLifecycle).pluginPathSubstitution); + const tokens = substitution.tokens; + if (!Array.isArray(tokens)) { + return {}; + } + const groups = asObject(substitution.fields); + const derived: JsonObject = {}; + for (const group of ['mcpStdio', 'mcpRemote']) { + const fields = groups[group]; + if (Array.isArray(fields)) { + for (const field of fields) { + if (typeof field === 'string') { + derived[field] = tokens; + } + } + } + } + return derived; +}; + +const escapeProse = (text: string): string => + text + .replaceAll('|', '\\|') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('{', '{') + .replaceAll('}', '}') + .replaceAll('\n', ' '); + +const code = (text: string): string => `\`${text.replaceAll('|', '\\|')}\``; + +const codeList = (values: readonly JsonValue[]): string => + values.map(value => code(String(value))).join(', '); + +const table = (headers: readonly string[], rows: readonly (readonly string[])[]): string => { + const line = (cells: readonly string[]) => `| ${cells.join(' | ')} |`; + return [ + line(headers), + line(headers.map(() => '---')), + ...rows.map(row => line(row)), + ].join('\n'); +}; + +const messages = { + en: { + generatedFromCapabilities: (files: readonly string[]) => + `:::info Generated page\nThis page is rendered at documentation build time from the pinned host capability tables ${files.map(file => `[\`${file}\`](${repositoryUrl}/packages/agent-bundle/src/adapters/capabilities/${file})`).join(', ')}. Those JSON files are the source of truth every target adapter compiles against; change them, not this page.\n:::`, + generatedFromDiagnostics: + `:::info Generated page\nThis page is a build-time copy of [\`docs/diagnostics.md\`](${repositoryUrl}/docs/diagnostics.md), the repository's diagnostics contract. Change that file, not this page.\n:::`, + hostsTitle: 'Host capability matrix', + hostsDescription: + 'Pinned host capability tables for the Claude Code, Codex, Cursor, and portable targets: observed versions, manifest locations, install surfaces, path tokens, MCP transports, and plugin components.', + hostsIntro: + 'Every target adapter projects the normalized bundle against a pinned capability table: a JSON document recording the host version the evidence was observed against, the paths the host reads, and — for each capability — whether it is `supported`, `degraded`, or `unavailable` with a written reason. Nothing is inferred: a capability without evidence is unavailable, never a silent guess.', + pinnedHosts: 'Pinned hosts', + installSurface: 'Install surface', + pathTokens: 'Path tokens', + mcpTransports: 'MCP transports and token fields', + pluginComponents: 'Plugin components', + pluginComponentsIntro: + 'The `plugin` section of each table, flattened to dotted capability paths. Boolean rows record a component the adapter emits; rows with a state carry the reason the host evidence supports or withholds it. Evidence notes stay in the JSON files.', + headers: { + host: 'Host', + version: 'Observed version', + manifest: 'Plugin manifest', + marketplace: 'Marketplace manifest', + hooksConfig: 'Hooks document', + state: 'State', + method: 'Method / commands', + scopes: 'Scopes', + source: 'Source', + token: 'Token', + capability: 'Capability', + detail: 'Detail', + stdio: 'stdio', + streamableHttp: 'Streamable HTTP', + tokenFields: 'Fields accepting path tokens', + canonicalEvent: 'Canonical event', + configKey: 'Config key', + selector: 'Selector', + nativeEvent: 'Native event', + reason: 'Reason', + channel: 'Channel', + hosts: 'Hosts', + }, + unavailable: 'unavailable', + notApplicable: '—', + // Hosts whose capability table records no MCP token fields because the + // adapter lowers the token instead of the host interpolating it. + mcpPathTokenNotes: { + codex: + 'No host interpolation. `command`, `args`, `env` values, and `cwd` accept a *leading* `${PLUGIN_ROOT}` only when `cwd` is the plugin root; the compiler rewrites it to a `./`-relative path under `cwd: "./"`. Embedded tokens, `${PLUGIN_DATA}`, and workspace-root tokens are build errors.', + } as Readonly>, + evidenceNotes: (count: number) => `${count} evidence note${count === 1 ? '' : 's'}`, + eventsTitle: 'Event and hook matrix', + eventsDescription: + 'Canonical event routes per host, config-declared hook events, canonical tool selectors mapped to native matchers, and the host-native events deliberately deferred.', + eventsIntro: + 'Event routes under `src/events/**` and config-declared `hooks` both compile against the same per-host tables. A canonical event lowers to the host-native event named here. A host without that event has nothing to lower to, so a route or hook that still selects that host fails the build (`.hook.event.*`, or `AB4204` for an explicit config-hook target): exclude the host through the route\'s `config.targets` or the hook\'s `targets`. A config hook with no explicit `targets` inherits only the selected hosts that support hooks, so unsupported hosts are skipped there without a diagnostic. Every unavailable cell records its reason rather than inferring one.', + eventRoutes: 'Canonical event routes', + eventRoutesIntro: + 'Rows are the canonical event families a `src/events//*.tsx` route may declare; columns are the pinned hosts. A cell names the native event the route lowers to.', + unavailableRoutes: 'Why a route is unavailable', + configHookEvents: 'Config-declared hook events', + configHookEventsIntro: + 'The `hooks` block of `agent-bundle.config.ts` is keyed by these canonical names; each maps to the native event a target registers.', + toolSelectors: 'Canonical tool selectors', + toolSelectorsIntro: + 'A hook or event route that scopes `tools` uses these canonical selectors; each adapter emits the native matcher below. A selector a target cannot map is a per-target diagnostic, never an empty matcher.', + deferredNativeEvents: 'Deferred host-native events', + deferredNativeEventsIntro: + 'Native events each host publishes that have no canonical route family yet, with the recorded reason for deferring them.', + noticesTitle: 'Notice delivery matrix', + noticesDescription: + 'Which notice delivery channels each pinned host supports, with the recorded reason for every unavailable channel.', + noticesIntro: + 'A notice is an entry in the append-only notice ledger co-mounted with project state (the reserved store id `@agent-bundle/runtime/agent-notice-ledger/v1`). It targets a recipient and moves only through evidenced states — `pending`, `attempted`, `acknowledged`, `expired`, `unavailable`, `withdrawn`. Delivery is attempted through the channels below; a recipient-scoped MCP inbox resource, `agent-bundle://notices/inbox`, exposes pending notices. No host delivery is claimed without a supported channel.', + noticeChannels: 'Delivery channels', + unavailableChannels: 'Why a channel is unavailable', + diagnosticsTitle: 'Diagnostics reference', + diagnosticsDescription: + 'Every agent-bundle diagnostic code family, severity, trigger, and recovery hint, copied at build time from the repository diagnostics contract.', + }, + zh: { + generatedFromCapabilities: (files: readonly string[]) => + `:::info 生成页面\n本页在文档构建时由固定版本的宿主能力表 ${files.map(file => `[\`${file}\`](${repositoryUrl}/packages/agent-bundle/src/adapters/capabilities/${file})`).join('、')} 渲染而成。这些 JSON 文件是每个目标适配器编译时依赖的唯一事实来源;请修改它们而不是本页。表格内容保留英文原文。\n:::`, + generatedFromDiagnostics: + `:::info 生成页面\n本页是仓库诊断契约 [\`docs/diagnostics.md\`](${repositoryUrl}/docs/diagnostics.md) 在构建时的副本,内容保留英文原文。请修改该文件而不是本页。\n:::`, + hostsTitle: '宿主能力矩阵', + hostsDescription: + 'Claude Code、Codex、Cursor 与 portable 目标的固定宿主能力表:观测版本、清单位置、安装方式、路径令牌、MCP 传输与插件组件。', + hostsIntro: + '每个目标适配器都会把归一化后的 bundle 投影到一份固定的能力表上:这份 JSON 文档记录了证据所对应的宿主版本、宿主读取的路径,以及每项能力是 `supported`、`degraded` 还是 `unavailable`,并附带书面原因。没有任何推断:缺少证据的能力即为不可用,绝不会默默猜测。', + pinnedHosts: '固定宿主', + installSurface: '安装方式', + pathTokens: '路径令牌', + mcpTransports: 'MCP 传输与令牌字段', + pluginComponents: '插件组件', + pluginComponentsIntro: + '每张表的 `plugin` 部分,按点分能力路径展开。布尔行表示适配器会发出的组件;带状态的行记录宿主证据支持或保留该能力的原因。证据说明保留在 JSON 文件中。', + headers: { + host: '宿主', + version: '观测版本', + manifest: '插件清单', + marketplace: '市场清单', + hooksConfig: '钩子文档', + state: '状态', + method: '方式 / 命令', + scopes: '作用域', + source: '来源', + token: '令牌', + capability: '能力', + detail: '说明', + stdio: 'stdio', + streamableHttp: 'Streamable HTTP', + tokenFields: '接受路径令牌的字段', + canonicalEvent: '规范事件', + configKey: '配置键', + selector: '选择器', + nativeEvent: '宿主原生事件', + reason: '原因', + channel: '通道', + hosts: '宿主', + }, + unavailable: 'unavailable', + notApplicable: '—', + mcpPathTokenNotes: { + codex: + '宿主不做插值。`command`、`args`、`env` 取值与 `cwd` 只在 `cwd` 为插件根时接受*开头*的 `${PLUGIN_ROOT}`;编译器会把它改写为 `cwd: "./"` 之下的 `./` 相对路径。嵌在中间的 token、`${PLUGIN_DATA}` 与工作区根 token 都是构建错误。', + } as Readonly>, + evidenceNotes: (count: number) => `${count} 条证据说明`, + eventsTitle: '事件与钩子矩阵', + eventsDescription: + '各宿主的规范事件路由、配置声明的钩子事件、规范工具选择器到原生匹配器的映射,以及被有意推迟的宿主原生事件。', + eventsIntro: + '`src/events/**` 下的事件路由与配置声明的 `hooks` 都基于同一组按宿主固定的表编译。规范事件会降级为此处列出的宿主原生事件。不具备该事件的宿主没有可降级的目标,因此仍然选中该宿主的路由或 hook 会让构建失败(`.hook.event.*`,显式配置 hook 目标则为 `AB4204`):请通过路由的 `config.targets` 或 hook 的 `targets` 排除该宿主。未显式声明 `targets` 的配置 hook 只继承所选宿主中支持 hook 的那些,因此不受支持的宿主会在那里被跳过且不产生诊断。每个不可用单元格都记录其原因,而非推断。', + eventRoutes: '规范事件路由', + eventRoutesIntro: + '行是 `src/events//*.tsx` 路由可以声明的规范事件族;列是固定宿主。单元格给出该路由降级到的原生事件。', + unavailableRoutes: '路由不可用的原因', + configHookEvents: '配置声明的钩子事件', + configHookEventsIntro: + '`agent-bundle.config.ts` 的 `hooks` 块以这些规范名称为键;每个键映射到目标注册的原生事件。', + toolSelectors: '规范工具选择器', + toolSelectorsIntro: + '限定 `tools` 的钩子或事件路由使用这些规范选择器;每个适配器发出下表中的原生匹配器。目标无法映射的选择器会产生针对该目标的诊断,而不是空匹配器。', + deferredNativeEvents: '推迟的宿主原生事件', + deferredNativeEventsIntro: + '各宿主公布但尚无规范路由族的原生事件,以及推迟它们的记录原因。', + noticesTitle: '通知投递矩阵', + noticesDescription: + '每个固定宿主支持哪些通知投递通道,以及每个不可用通道的记录原因。', + noticesIntro: + '通知是与项目状态共同挂载的只追加通知账本中的一条记录(保留的存储 id 为 `@agent-bundle/runtime/agent-notice-ledger/v1`)。它面向一个接收者,并且只会经历有证据的状态——`pending`、`attempted`、`acknowledged`、`expired`、`unavailable`、`withdrawn`。投递通过下列通道尝试;按接收者限定的 MCP 收件箱资源 `agent-bundle://notices/inbox` 会暴露待处理通知。没有受支持的通道时,绝不声称已投递到宿主。', + noticeChannels: '投递通道', + unavailableChannels: '通道不可用的原因', + diagnosticsTitle: '诊断参考', + diagnosticsDescription: + 'agent-bundle 的全部诊断代码族、严重级别、触发条件与恢复提示,在构建时从仓库诊断契约复制而来。', + }, +} as const; + +type Messages = (typeof messages)[keyof typeof messages]; + +const frontmatter = (title: string, description: string): string => + `---\ntitle: ${JSON.stringify(title)}\ndescription: ${JSON.stringify(description)}\n---\n`; + +async function loadCapabilityTables(repoRoot: string): Promise { + const directory = path.join(repoRoot, ...capabilitiesDir); + const entries = (await readdir(directory)).filter(name => name.endsWith('.json')).sort(); + const tables: HostCapabilityTable[] = []; + for (const fileName of entries) { + const parsed = JSON.parse(await readFile(path.join(directory, fileName), 'utf8')) as JsonValue; + if (!isObject(parsed)) { + throw new Error(`Capability table ${fileName} is not a JSON object.`); + } + const host = asString(parsed.host); + if (host === undefined) { + throw new Error(`Capability table ${fileName} has no "host" field.`); + } + const version = + asString(parsed.observedCliVersion) ?? asString(parsed.observedSpecificationVersion) ?? 'unpinned'; + tables.push({ data: parsed, fileName, host, version }); + } + if (tables.length === 0) { + throw new Error(`No capability tables found under ${directory}.`); + } + return tables; +} + +const hostHeader = (host: HostCapabilityTable): string => `${host.host} ${host.version}`; + +const capabilityRow = (value: JsonValue | undefined): CapabilityRow | undefined => + isObject(value) ? (value as unknown as CapabilityRow) : undefined; + +const stateCell = (row: CapabilityRow | undefined, m: Messages): string => { + if (row === undefined) { + return m.notApplicable; + } + if (row.state === 'supported') { + const parts: string[] = []; + if (row.nativeEvent !== undefined) { + parts.push(code(row.nativeEvent)); + } else { + parts.push('supported'); + } + if (row.availability !== undefined) { + const perSurface = Object.entries(row.availability) + .map(([surface, availability]) => `${surface}: ${availability.state ?? 'unknown'}`) + .join('; '); + parts.push(`(${perSurface})`); + } + return parts.join(' '); + } + return row.state ?? m.unavailable; +}; + +const unionKeys = (hosts: readonly HostCapabilityTable[], select: (data: JsonObject) => JsonObject): string[] => + [...new Set(hosts.flatMap(host => Object.keys(select(host.data))))].sort(); + +const eventRoutesOf = (data: JsonObject): JsonObject => asObject(asObject(data.hooks).eventRoutes ?? data.eventRoutes); + +interface FlattenedRow { + readonly path: string; + readonly state: string; + readonly detail: string; +} + +function flattenPlugin(value: JsonObject, prefix: string, m: Messages, rows: FlattenedRow[]): void { + for (const [key, entry] of Object.entries(value)) { + const dotted = prefix ? `${prefix}.${key}` : key; + if (isObject(entry)) { + if (typeof entry.state === 'string') { + const details: string[] = []; + if (typeof entry.reason === 'string') { + details.push(escapeProse(entry.reason)); + } + for (const [detailKey, detailValue] of Object.entries(entry)) { + if (detailKey === 'state' || detailKey === 'reason' || detailKey === 'evidence') { + continue; + } + if (Array.isArray(detailValue)) { + details.push(`${code(detailKey)}: ${codeList(detailValue)}`); + } else if (isObject(detailValue)) { + details.push(`${code(detailKey)}: ${code(JSON.stringify(detailValue))}`); + } else { + details.push(`${code(detailKey)}: ${code(String(detailValue))}`); + } + } + if (Array.isArray(entry.evidence)) { + details.push(m.evidenceNotes(entry.evidence.length)); + } + rows.push({ detail: details.join('
'), path: dotted, state: entry.state }); + continue; + } + flattenPlugin(entry, dotted, m, rows); + continue; + } + if (Array.isArray(entry)) { + rows.push({ detail: codeList(entry), path: dotted, state: m.notApplicable }); + continue; + } + if (typeof entry === 'boolean') { + rows.push({ detail: m.notApplicable, path: dotted, state: entry ? 'supported' : 'unavailable' }); + continue; + } + rows.push({ detail: code(String(entry)), path: dotted, state: m.notApplicable }); + } +} + +function renderHosts(hosts: readonly HostCapabilityTable[], m: Messages): string { + const sections: string[] = []; + sections.push(frontmatter(m.hostsTitle, m.hostsDescription)); + sections.push(`# ${m.hostsTitle}\n`); + sections.push(m.generatedFromCapabilities(hosts.map(host => host.fileName))); + sections.push(m.hostsIntro); + + sections.push(`## ${m.pinnedHosts}\n`); + sections.push( + table( + [m.headers.host, m.headers.version, m.headers.manifest, m.headers.marketplace, m.headers.hooksConfig], + hosts.map(host => { + const plugin = asObject(host.data.plugin); + const hooks = asObject(host.data.hooks); + return [ + code(host.host), + code(host.version), + asString(plugin.manifest) === undefined ? m.notApplicable : code(asString(plugin.manifest) ?? ''), + asString(plugin.marketplace) === undefined ? m.notApplicable : code(asString(plugin.marketplace) ?? ''), + asString(hooks.config) === undefined ? m.notApplicable : code(asString(hooks.config) ?? ''), + ]; + }), + ), + ); + + sections.push(`## ${m.installSurface}\n`); + sections.push( + table( + [m.headers.host, m.headers.state, m.headers.method, m.headers.scopes, m.headers.source], + hosts.map(host => { + const install = asObject(host.data.install); + const methods: string[] = []; + for (const key of ['marketplaceAdd', 'pluginInstall', 'method', 'localRoot'] as const) { + const value = asString(install[key]); + if (value !== undefined) { + methods.push(`${code(key)}: ${code(value)}`); + } + } + const reason = asString(install.reason); + if (reason !== undefined) { + methods.push(escapeProse(reason)); + } + const scopes = Array.isArray(install.scopes) ? codeList(install.scopes) : m.notApplicable; + const source = asString(install.source); + return [ + code(host.host), + asString(install.state) ?? m.unavailable, + methods.length > 0 ? methods.join('
') : m.notApplicable, + scopes, + source === undefined ? m.notApplicable : `[${source}](${source})`, + ]; + }), + ), + ); + + sections.push(`## ${m.pathTokens}\n`); + const tokenKeys = unionKeys(hosts, data => asObject(data.tokens)); + sections.push( + table( + [m.headers.token, ...hosts.map(hostHeader)], + tokenKeys.map(tokenKey => [ + code(tokenKey), + ...hosts.map(host => { + const value = asObject(host.data.tokens)[tokenKey]; + if (value === undefined || value === false) { + return m.notApplicable; + } + return code(String(value)); + }), + ]), + ), + ); + + sections.push(`## ${m.mcpTransports}\n`); + sections.push( + table( + [m.headers.host, m.headers.stdio, m.headers.streamableHttp, m.headers.tokenFields], + hosts.map(host => { + const mcp = asObject(host.data.mcp); + const fields = Object.entries(mcpPathTokenFields(host.data)) + .map(([field, tokens]) => `${code(field)}: ${Array.isArray(tokens) ? codeList(tokens) : m.notApplicable}`) + .join('
'); + const note = m.mcpPathTokenNotes[host.host]; + return [ + code(host.host), + mcp.stdio === true ? 'supported' : m.unavailable, + mcp.streamableHttp === true ? 'supported' : m.unavailable, + fields.length > 0 ? fields : note ?? m.notApplicable, + ]; + }), + ), + ); + + sections.push(`## ${m.pluginComponents}\n`); + sections.push(m.pluginComponentsIntro); + for (const host of hosts) { + const rows: FlattenedRow[] = []; + flattenPlugin(asObject(host.data.plugin), '', m, rows); + sections.push(`### ${hostHeader(host)}\n`); + sections.push( + table( + [m.headers.capability, m.headers.state, m.headers.detail], + rows.map(row => [code(row.path), row.state, row.detail]), + ), + ); + } + + return `${sections.join('\n\n')}\n`; +} + +function renderEvents(hosts: readonly HostCapabilityTable[], m: Messages): string { + const sections: string[] = []; + sections.push(frontmatter(m.eventsTitle, m.eventsDescription)); + sections.push(`# ${m.eventsTitle}\n`); + sections.push(m.generatedFromCapabilities(hosts.map(host => host.fileName))); + sections.push(m.eventsIntro); + + sections.push(`## ${m.eventRoutes}\n`); + sections.push(m.eventRoutesIntro); + const eventKeys = unionKeys(hosts, eventRoutesOf); + sections.push( + table( + [m.headers.canonicalEvent, ...hosts.map(hostHeader)], + eventKeys.map(eventKey => [ + code(eventKey), + ...hosts.map(host => stateCell(capabilityRow(eventRoutesOf(host.data)[eventKey]), m)), + ]), + ), + ); + + sections.push(`## ${m.unavailableRoutes}\n`); + for (const host of hosts) { + const routes = eventRoutesOf(host.data); + const unavailable = Object.entries(routes) + .map(([eventKey, value]) => [eventKey, capabilityRow(value)] as const) + .filter(([, row]) => row !== undefined && row.state !== 'supported'); + const surfaceLimited = Object.entries(routes) + .map(([eventKey, value]) => [eventKey, capabilityRow(value)] as const) + .filter(([, row]) => row?.availability !== undefined); + if (unavailable.length === 0 && surfaceLimited.length === 0) { + continue; + } + sections.push(`### ${hostHeader(host)}\n`); + const bullets: string[] = []; + for (const [eventKey, row] of unavailable) { + bullets.push(`- ${code(eventKey)} — ${escapeProse(row?.reason ?? m.unavailable)}`); + } + for (const [eventKey, row] of surfaceLimited) { + for (const [surface, availability] of Object.entries(row?.availability ?? {})) { + if (availability.state !== 'supported') { + bullets.push( + `- ${code(eventKey)} (${surface}) — ${escapeProse(availability.reason ?? availability.state ?? m.unavailable)}`, + ); + } + } + } + sections.push(bullets.join('\n')); + } + + sections.push(`## ${m.configHookEvents}\n`); + sections.push(m.configHookEventsIntro); + const hookHosts = hosts.filter(host => Object.keys(asObject(asObject(host.data.hooks).events)).length > 0); + const configKeys = unionKeys(hookHosts, data => asObject(asObject(data.hooks).events)); + sections.push( + table( + [m.headers.configKey, ...hookHosts.map(hostHeader)], + configKeys.map(configKey => [ + code(configKey), + ...hookHosts.map(host => { + const native = asString(asObject(asObject(host.data.hooks).events)[configKey]); + return native === undefined ? m.notApplicable : code(native); + }), + ]), + ), + ); + + sections.push(`## ${m.toolSelectors}\n`); + sections.push(m.toolSelectorsIntro); + const matcherHosts = hosts.filter(host => Object.keys(asObject(asObject(host.data.hooks).matchers)).length > 0); + const selectorKeys = unionKeys(matcherHosts, data => asObject(asObject(data.hooks).matchers)); + sections.push( + table( + [m.headers.selector, ...matcherHosts.map(hostHeader)], + selectorKeys.map(selector => [ + code(selector), + ...matcherHosts.map(host => { + const matcher = asString(asObject(asObject(host.data.hooks).matchers)[selector]); + return matcher === undefined ? m.notApplicable : code(matcher); + }), + ]), + ), + ); + + sections.push(`## ${m.deferredNativeEvents}\n`); + sections.push(m.deferredNativeEventsIntro); + for (const host of hosts) { + const deferred = asObject(host.data.deferredNativeEvents); + const entries = Object.entries(deferred).sort(([left], [right]) => left.localeCompare(right)); + if (entries.length === 0) { + continue; + } + sections.push(`### ${hostHeader(host)}\n`); + sections.push( + table( + [m.headers.nativeEvent, m.headers.state, m.headers.reason], + entries.map(([nativeEvent, value]) => { + const row = capabilityRow(value); + return [code(nativeEvent), row?.state ?? m.unavailable, escapeProse(row?.reason ?? '')]; + }), + ), + ); + } + + return `${sections.join('\n\n')}\n`; +} + +function renderNotices(hosts: readonly HostCapabilityTable[], m: Messages): string { + const sections: string[] = []; + sections.push(frontmatter(m.noticesTitle, m.noticesDescription)); + sections.push(`# ${m.noticesTitle}\n`); + sections.push(m.generatedFromCapabilities(hosts.map(host => host.fileName))); + sections.push(m.noticesIntro); + + sections.push(`## ${m.noticeChannels}\n`); + const channels = unionKeys(hosts, data => asObject(data.noticeDelivery)); + sections.push( + table( + [m.headers.channel, ...hosts.map(hostHeader)], + channels.map(channel => [ + code(channel), + ...hosts.map(host => stateCell(capabilityRow(asObject(host.data.noticeDelivery)[channel]), m)), + ]), + ), + ); + + sections.push(`## ${m.unavailableChannels}\n`); + const reasons = new Map(); + for (const host of hosts) { + for (const [channel, value] of Object.entries(asObject(host.data.noticeDelivery))) { + const row = capabilityRow(value); + if (row === undefined || row.state === 'supported') { + continue; + } + const key = `${channel}\u0000${row.reason ?? ''}`; + const existing = reasons.get(key); + if (existing !== undefined) { + existing.hosts.push(host.host); + } else { + reasons.set(key, { channel, hosts: [host.host] }); + } + } + } + sections.push( + table( + [m.headers.channel, m.headers.hosts, m.headers.reason], + [...reasons.entries()] + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, entry]) => [ + code(entry.channel), + entry.hosts.map(code).join(', '), + escapeProse(key.split('\u0000')[1] ?? ''), + ]), + ), + ); + + return `${sections.join('\n\n')}\n`; +} + +async function renderDiagnostics(repoRoot: string, m: Messages): Promise { + const source = await readFile(path.join(repoRoot, ...diagnosticsSource), 'utf8'); + const lines = source.split('\n'); + const headingIndex = lines.findIndex(line => line.startsWith('# ')); + if (headingIndex === -1) { + throw new Error('docs/diagnostics.md has no top-level heading.'); + } + const body = rewriteRepoDocLinks(lines.slice(headingIndex + 1).join('\n').trim()); + return `${frontmatter(m.diagnosticsTitle, m.diagnosticsDescription)}\n# ${m.diagnosticsTitle}\n\n${m.generatedFromDiagnostics}\n\n${body}\n`; +} + +/** + * `docs/diagnostics.md` links to sibling repository docs by relative path. + * Those siblings are not site pages, so the copy points them at GitHub + * instead of leaving dead links for Rspress to reject. + */ +function rewriteRepoDocLinks(markdown: string): string { + return markdown.replaceAll( + /\]\(([\w./-]+\.md)(#[\w-]*)?\)/g, + (_match, target: string, fragment: string | undefined) => + `](${new URL(target, `${repositoryUrl}/docs/`).href}${fragment ?? ''})`, + ); +} + +/** Render every generated reference page for one locale into `targetDir`. */ +export async function writeGeneratedReference( + options: GeneratedReferenceOptions, + docsRoot: string, +): Promise { + const hosts = await loadCapabilityTables(options.repoRoot); + for (const locale of options.locales) { + const m = messages[locale.lang]; + const targetDir = path.join(docsRoot, locale.dir, 'reference'); + await mkdir(targetDir, { recursive: true }); + const pages: Record<(typeof generatedReferencePages)[number], string> = { + diagnostics: await renderDiagnostics(options.repoRoot, m), + events: renderEvents(hosts, m), + hosts: renderHosts(hosts, m), + notices: renderNotices(hosts, m), + }; + for (const page of generatedReferencePages) { + await writeFile(path.join(targetDir, `${page}.md`), pages[page], 'utf8'); + } + } +} + +/** + * Registered after `pluginTypeDoc` and the locale mirror so every generated + * page exists before Rspress scans routes and checks links. + */ +export function generatedReference(options: GeneratedReferenceOptions): RspressPlugin { + return { + name: 'agent-bundle/generated-reference', + async config(config) { + if (!config.root) { + return config; + } + await writeGeneratedReference(options, config.root); + return config; + }, + }; +} diff --git a/website/plugins/mirror-api-locale.ts b/website/plugins/mirror-api-locale.ts new file mode 100644 index 000000000..de6f94d71 --- /dev/null +++ b/website/plugins/mirror-api-locale.ts @@ -0,0 +1,115 @@ +import { copyFile, mkdir, readdir, rm } from 'node:fs/promises'; +import path from 'node:path'; +import type { RspressPlugin } from '@rspress/core'; + +const MARKDOWN_EXTENSION = '.md'; + +async function readDirectory(directory: string) { + try { + return await readdir(directory, { withFileTypes: true }); + } catch { + return []; + } +} + +async function collectMarkdownFiles(directory: string, prefix = ''): Promise { + const entries = await readDirectory(directory); + const collected: string[] = []; + + for (const entry of entries) { + const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name; + + if (entry.isDirectory()) { + collected.push(...(await collectMarkdownFiles(path.join(directory, entry.name), relativePath))); + continue; + } + + if (entry.name.endsWith(MARKDOWN_EXTENSION)) { + collected.push(relativePath); + } + } + + return collected; +} + +async function prunePlaceholderDirectories(directory: string): Promise { + const entries = await readDirectory(directory); + let isEmpty = true; + + for (const entry of entries) { + if (!entry.isDirectory()) { + isEmpty = false; + continue; + } + + const child = path.join(directory, entry.name); + if (await prunePlaceholderDirectories(child)) { + await rm(child, { recursive: true, force: true }); + } else { + isEmpty = false; + } + } + + return isEmpty; +} + +/** + * Remove generated Markdown from an API directory while leaving the committed + * `_meta.json` sidebar in place, so a removed package export cannot survive as + * a phantom route. + */ +export async function cleanGeneratedApiMarkdown(directory: string): Promise { + for (const relativePath of await collectMarkdownFiles(directory)) { + await rm(path.join(directory, relativePath), { force: true }); + } + + await prunePlaceholderDirectories(directory); +} + +export interface MirrorApiLocaleOptions { + /** Docs-root-relative directory that TypeDoc generates, such as `en/api`. */ + sourceDir: string; + /** Docs-root-relative directories that receive the mirrored Markdown. */ + targetDirs: string[]; +} + +/** + * Mirror the TypeDoc reference into the remaining locale roots. + * + * Symbols, signatures, and source comments are one package-level contract, so + * a single TypeDoc run is copied instead of paying for a second TypeScript + * program. Only `.md` files travel: each locale keeps its own translated + * `_meta.json` sidebar. + * + * Must be registered immediately after `pluginTypeDoc` so the `config` hook + * runs once the English reference has been written and before route scanning. + */ +export function mirrorApiLocale(options: MirrorApiLocaleOptions): RspressPlugin { + const { sourceDir, targetDirs } = options; + + return { + name: 'agent-bundle/mirror-api-locale', + async config(config) { + const docsRoot = config.root; + if (!docsRoot) { + return config; + } + + const source = path.join(docsRoot, sourceDir); + const generatedFiles = await collectMarkdownFiles(source); + + for (const targetDir of targetDirs) { + const target = path.join(docsRoot, targetDir); + await cleanGeneratedApiMarkdown(target); + + for (const relativePath of generatedFiles) { + const destination = path.join(target, relativePath); + await mkdir(path.dirname(destination), { recursive: true }); + await copyFile(path.join(source, relativePath), destination); + } + } + + return config; + }, + }; +} diff --git a/website/rspress.config.ts b/website/rspress.config.ts new file mode 100644 index 000000000..4cfd1f4db --- /dev/null +++ b/website/rspress.config.ts @@ -0,0 +1,190 @@ +import path from 'node:path'; +import { defineConfig } from '@rspress/core'; +import { pluginLlms } from '@rspress/plugin-llms'; +import { pluginSitemap } from '@rspress/plugin-sitemap'; +import { pluginTwoslash } from '@rspress/plugin-twoslash'; +import { pluginTypeDoc } from '@rspress/plugin-typedoc'; +import { + transformerNotationDiff, + transformerNotationFocus, + transformerNotationHighlight, +} from '@shikijs/transformers'; +import { generatedReference } from './plugins/generated-reference.ts'; +import { cleanGeneratedApiMarkdown, mirrorApiLocale } from './plugins/mirror-api-locale.ts'; + +const websiteDir = import.meta.dirname; +const docsDir = path.join(websiteDir, 'docs'); +const repoRoot = path.join(websiteDir, '..'); + +const packageSource = path.join(repoRoot, 'packages', 'agent-bundle', 'src'); + +const publicApiEntryPoints = [ + 'index.ts', + 'api.ts', + 'cli-entry.ts', + 'config/index.ts', + 'eval/index.ts', + 'mcp-apps.ts', + 'meta.ts', + 'mcp-entry.ts', + 'routes/public.ts', + 'rstest/index.ts', + 'test/index.ts', + 'test/browser.ts', +].map(entry => path.join(packageSource, entry)); + +const generatedApiDir = 'en/api'; +const mirroredApiDirs = ['zh/api']; + +const repositoryUrl = 'https://github.com/ScriptedAlchemy/agent-bundle'; +const siteTitle = 'agent-bundle'; +const siteDescription = + 'Compile skills, hooks, MCP servers, and scripts from one typed config into installable Claude Code, Codex, and Cursor artifacts.'; +const siteDescriptionZh = + '用一份带类型的配置描述 Skill、钩子、MCP 服务器与脚本,编译为可直接安装到 Claude Code、Codex 与 Cursor 的产物。'; + +/** + * `llms.txt` and `llms-full.txt` are emitted as build assets rather than + * routes, so the route-based dead-link check cannot see them. + */ +const isGeneratedLlmsTarget = (url: string): boolean => /(?:^|\/)llms(?:-full)?\.txt$/.test(url); + +export default defineConfig({ + root: docsDir, + base: '/agent-bundle/', + siteOrigin: 'https://scriptedalchemy.github.io', + globalStyles: path.join(websiteDir, 'styles/index.css'), + lang: 'en', + title: siteTitle, + description: siteDescription, + icon: '/logo.svg', + logo: '/logo.svg', + logoText: siteTitle, + locales: [ + { + lang: 'en', + label: 'English', + title: siteTitle, + description: siteDescription, + }, + { + lang: 'zh', + label: '简体中文', + title: siteTitle, + description: siteDescriptionZh, + }, + ], + search: { + codeBlocks: true, + }, + route: { + cleanUrls: true, + localeRedirect: 'never', + }, + markdown: { + shiki: { + // Twoslash hovers render JSDoc code fences from dependency types, so the + // grammar set cannot be inferred from page sources alone (same fix as + // the upstream rspress.rs site). + langs: ['markdown', 'mdx', 'ts', 'tsx', 'js', 'jsx', 'json', 'bash', 'yaml', 'css', 'html'], + transformers: [ + transformerNotationDiff(), + transformerNotationHighlight(), + transformerNotationFocus(), + ], + }, + link: { + checkDeadLinks: { excludes: isGeneratedLlmsTarget }, + checkAnchors: true, + }, + image: { + checkDeadImages: true, + }, + }, + // `include` entries are directories, so listing the homepage there would + // silently drop it from the check. The default walks each locale root. + languageParity: { + enabled: true, + exclude: ['api'], + }, + themeConfig: { + llmsUI: { placement: 'outline' }, + editLink: { + docRepoBaseUrl: `${repositoryUrl}/tree/main/website/docs`, + }, + socialLinks: [{ icon: 'github', mode: 'link', content: repositoryUrl }], + }, + plugins: [ + // TypeDoc and twoslash compile packages/agent-bundle/src with the + // `typescript` pinned in website/package.json. That pin is TypeScript 6 + // because typedoc 0.28 peers on `<= 6.0.x` while the repo root is on + // TypeScript 7, so TS7-only syntax in the package fails here first, with + // an error that names the docsite rather than the cause. + pluginTypeDoc({ + entryPoints: publicApiEntryPoints, + outDir: generatedApiDir, + setup: async app => { + await cleanGeneratedApiMarkdown(path.join(docsDir, generatedApiDir)); + // Rspress derives sidebar and prev/next labels from the raw `# ` line, + // so typedoc-plugin-markdown's escaped underscores (`FOO\_BAR`) would + // surface verbatim. Intraword underscores are not emphasis in + // CommonMark, so the member title is safe to emit unescaped. + app.options.setValue('pageTitleTemplates', { + index: '{projectName} {version}', + module: '{kind}: {name}', + member: ({ kind, name }: { kind: string; name: string }) => + `${kind}: ${name.replace(/\\_/g, '_')}`, + }); + return app; + }, + }), + mirrorApiLocale({ + sourceDir: generatedApiDir, + targetDirs: mirroredApiDirs, + }), + generatedReference({ + repoRoot, + locales: [ + { lang: 'en', dir: 'en' }, + { lang: 'zh', dir: 'zh' }, + ], + }), + pluginTwoslash({ + twoslashOptions: { + compilerOptions: { + paths: { + 'agent-bundle': [path.join(packageSource, 'index.ts')], + 'agent-bundle/config': [path.join(packageSource, 'config/index.ts')], + 'agent-bundle/test': [path.join(packageSource, 'test/index.ts')], + 'agent-bundle/eval': [path.join(packageSource, 'eval/index.ts')], + }, + }, + }, + }), + // Passing options replaces the plugin's per-locale defaults instead of + // merging with them, so both locale outputs are spelled out here. + pluginLlms([ + { + llmsTxt: { name: 'llms.txt' }, + llmsFullTxt: { name: 'llms-full.txt' }, + // Strip theme imports and unwrap JSX so the homepage ships as Markdown. + mdFiles: { mdxToMd: true }, + include: ({ page }) => page.lang === 'en', + exclude: ({ page }) => page.routePath.includes('/api/'), + }, + { + // The plugin reads the site-level title and description, so the + // Chinese summary has to be restated here. + llmsTxt: { + name: 'zh/llms.txt', + onTitleGenerate: () => `# ${siteTitle}\n\n> ${siteDescriptionZh}`, + }, + llmsFullTxt: { name: 'zh/llms-full.txt' }, + mdFiles: { mdxToMd: true }, + include: ({ page }) => page.lang === 'zh', + exclude: ({ page }) => page.routePath.includes('/api/'), + }, + ]), + pluginSitemap(), + ], +}); diff --git a/website/styles/index.css b/website/styles/index.css new file mode 100644 index 000000000..bacf35e18 --- /dev/null +++ b/website/styles/index.css @@ -0,0 +1,108 @@ +/* + * agent-bundle brand tokens. + * + * Rspress declares its own tokens through `:where(...)` (zero specificity), so + * plain `:root` selectors here win regardless of stylesheet order. + */ + +:root:not(.rp-dark) { + --rp-c-brand: #0d8f80; + --rp-c-brand-light: #14b8a6; + --rp-c-brand-lighter: #99f6e4; + --rp-c-brand-dark: #0f766e; + --rp-c-brand-darker: #115e59; + --rp-c-brand-tint: #14b8a629; + + --rp-home-hero-secondary-color: #5b5bd6; +} + +:root.rp-dark { + --rp-c-brand: #2dd4bf; + --rp-c-brand-light: #5eead4; + --rp-c-brand-lighter: #99f6e4; + --rp-c-brand-dark: #14b8a6; + --rp-c-brand-darker: #0d9488; + --rp-c-brand-tint: #2dd4bf29; + + --rp-home-hero-secondary-color: #a5b4fc; +} + +/* + * The hero title inherits `--rp-home-hero-title-bg` from the default theme, + * which references the brand tokens above, so only the gradient endpoint is + * restated to keep the second stop inside the agent-bundle palette. + */ +:root { + --rp-home-hero-title-bg: linear-gradient( + 90deg, + var(--rp-c-brand-dark) 0%, + var(--rp-c-brand-dark) 32%, + var(--rp-home-hero-secondary-color) 100% + ); +} + +/* + * Bounded homepage accent. The default theme already brand-colors the feature + * card border on hover, so this only tints the icon chip and adds a small lift. + */ +.rp-home-feature__icon { + border-color: var(--rp-c-brand-tint); + background-color: var(--rp-c-bg-soft); +} + +.rp-home-feature__card:hover { + transform: translateY(-2px); +} + +/* + * Home page body, rendered by the theme's `afterFeatures` slot. Width matches + * the feature grid container so the sections line up with the cards above. + */ +.ab-home-body { + max-width: 72rem; + margin: 0 auto; + padding: 1rem 1.5rem 4rem; +} + +.ab-home-body .rp-doc h2 { + margin-top: 3.5rem; + font-size: 1.75rem; + letter-spacing: -0.01em; +} + +.ab-home-body .rp-doc h2 + p { + color: var(--rp-c-text-2); + font-size: 1.05rem; + max-width: 48rem; +} + +/* Side-by-side "what you write / what you get" comparison. */ +.ab-compare { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + gap: 1.5rem; + align-items: start; + margin-top: 1.5rem; +} + +.ab-compare > div { + min-width: 0; + padding: 1.25rem 1.5rem 0.25rem; + border: 1px solid var(--rp-c-divider-light); + border-radius: 12px; + background: var(--rp-c-bg-soft); +} + +.ab-compare > div > h3 { + margin-top: 0; + font-size: 0.95rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--rp-c-brand); +} + +.ab-compare .rp-doc pre, +.ab-compare pre { + font-size: 0.82rem; +} diff --git a/website/theme/index.tsx b/website/theme/index.tsx new file mode 100644 index 000000000..d0b0a08fa --- /dev/null +++ b/website/theme/index.tsx @@ -0,0 +1,25 @@ +import { MDXProvider } from '@mdx-js/react'; +import { Content } from '@rspress/core/runtime'; +import { Layout as BasicLayout, getCustomMDXComponent } from '@rspress/core/theme-original'; + +/** + * The default `HomeLayout` renders only the frontmatter hero and feature cards + * and discards the page body. This slot renders the home page's MDX body below + * the feature grid with the standard doc components, so the landing pages can + * carry prose, code fences, tabs, and tables authored per locale in + * `docs//index.mdx`. + */ +const HomeBody = () => ( +
+
+ + + +
+
+); + +const Layout = () => } />; + +export { Layout }; +export * from '@rspress/core/theme-original'; diff --git a/website/tsconfig.json b/website/tsconfig.json new file mode 100644 index 000000000..e90196cce --- /dev/null +++ b/website/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "isolatedModules": true, + "jsx": "react-jsx", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2024", + "types": ["node", "react"] + }, + "include": ["rspress.config.ts", "plugins/**/*.ts", "theme/**/*.tsx"] +} diff --git a/website/tsconfig.typedoc.json b/website/tsconfig.typedoc.json new file mode 100644 index 000000000..44af7e068 --- /dev/null +++ b/website/tsconfig.typedoc.json @@ -0,0 +1,30 @@ +{ + // TypeDoc consumes the package's own build settings, but the documentation + // build must not depend on `pnpm build`: `packages/agent-bundle/src` imports + // `@agent-bundle/runtime`, whose declarations only exist after that package + // is compiled. Resolving those specifiers to `packages/rsc-runtime/src` + // keeps `pnpm docs:site:build` self-sufficient, mirroring the source-mapped `paths` + // the Twoslash plugin uses for the same reason. + "extends": "../packages/agent-bundle/tsconfig.build.json", + "compilerOptions": { + // Widened from the package's `src` so the resolved runtime sources are + // allowed in the program. TypeDoc never emits, so this only affects checks. + "rootDir": "../packages", + "paths": { + "@agent-bundle/runtime": ["../packages/rsc-runtime/src/index.ts"], + "@agent-bundle/runtime/plugin": ["../packages/rsc-runtime/src/plugin.ts"], + "@agent-bundle/runtime/flight/server": ["../packages/rsc-runtime/src/flight/server.ts"], + "@agent-bundle/runtime/mount": ["../packages/rsc-runtime/src/mount/index.ts"], + "@agent-bundle/runtime/notices": ["../packages/rsc-runtime/src/notices/index.ts"], + "@agent-bundle/runtime/notices/inbox-route": ["../packages/rsc-runtime/src/notices/inbox-route.ts"], + "@agent-bundle/runtime/state": ["../packages/rsc-runtime/src/state/index.ts"], + "@agent-bundle/runtime/state/sqlite": ["../packages/rsc-runtime/src/state/sqlite.ts"] + } + }, + "include": [ + "../packages/agent-bundle/src/**/*.ts", + // Ambient declarations for `react-server-dom-rspack`, which the runtime + // sources rely on and which an import graph alone will not pull in. + "../packages/rsc-runtime/src/react-server-dom-rspack.d.ts" + ] +} diff --git a/website/typedoc.json b/website/typedoc.json new file mode 100644 index 000000000..d5e14c17b --- /dev/null +++ b/website/typedoc.json @@ -0,0 +1,3 @@ +{ + "tsconfig": "./tsconfig.typedoc.json" +}