diff --git a/.changeset/framework-mode-agent-bundle.md b/.changeset/framework-mode-agent-bundle.md new file mode 100644 index 000000000..f22117fb3 --- /dev/null +++ b/.changeset/framework-mode-agent-bundle.md @@ -0,0 +1,19 @@ +--- +"agent-bundle": minor +--- + +Framework mode (RFC #63), compiler side. The `skills//` directory +convention gains a power tier and two migration nudges. Rendered skills: a +skill directory may hold `SKILL.tsx` (or `SKILL.ts`) instead of `SKILL.md` — +the module default-exports a component (sync or async) and exports a +`frontmatter` record, and the build renders the element tree to Markdown +through a dependency-free renderer covering a documented subset (`h1`–`h6`, +`p`, lists, `strong`/`em`/`code`, `pre`, `blockquote`, `a`, `hr`, `br`, +fragments; anything else is a named error, never a silent approximation). The +compiled `SKILL.md` is emitted as a generated write entry into every target +artifact. New nudges: `AB4734` when explicit `skills` configuration leaves a +conventional `skills//SKILL.md` uncovered (config wins, the shadowed +state is flagged), and `AB4735` when a hand-authored `SKILL.md` shadows a +rendered `SKILL.tsx`/`SKILL.ts` in the same directory (the authored file +wins). The `create-agent-bundle` minimal template now teaches the directory +convention: no `skills` field in its config at all. diff --git a/.changeset/framework-mode-rsc-runtime.md b/.changeset/framework-mode-rsc-runtime.md new file mode 100644 index 000000000..b5487cc3a --- /dev/null +++ b/.changeset/framework-mode-rsc-runtime.md @@ -0,0 +1,19 @@ +--- +"@agent-bundle/rsc-runtime": minor +--- + +Framework mode (RFC #63), runtime side — **breaking removals**. The +structural JSX layer is gone: the `AgentBundle`, `Skill`, `Script`, +`McpServer`, `McpApp`, and `Operation` elements, `defineRscAgentBundle`, and +the `RscAgentBundleApplication` type are removed outright. Structure — +targets, skills, scripts, servers, apps — is declared in +`agent-bundle.config.ts` and file conventions; JSX remains only where +something is rendered (`Mcp.*`/`Hook.*` results, rendered skill bodies). +Their replacement is `defineRscApplication({ name, version, description?, +operations })`: a flat, JSX-free declaration of the runtime identity and the +typed operation catalog, rejecting duplicate operation ids, CLI commands, and +MCP tools. `runRscCli` and `createRscMcpServer` now consume this flattened +application — `createRscMcpServer(application, serverName)` selects the +operations whose `mcp.server` matches and throws for a name no operation +references. The `agent-bundle` peer dependency is dropped; the package no +longer imports config types at all. diff --git a/README.md b/README.md index f57889115..83a841121 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ npx agent-bundle dev --root . # local workbench with live rebu `targets: ['plugin']` emits one multi-host bundle at `dist/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. -The same config also owns the npm package build — no second bundler config, bin shims, or 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. [Entry conventions](docs/entry-conventions.md) is the full contract. +The same config also owns the npm package build — no second bundler config, bin shims, or 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. [Entry conventions](docs/entry-conventions.md) is the full contract, and [Framework mode](docs/framework-mode.md) is the whole authoring model on one screen: structure in config and conventions (`skills//SKILL.md` ships with no declaration at all), JSX only where something is rendered. ## Commands diff --git a/docs/diagnostics.md b/docs/diagnostics.md index 36789e357..932ff0bdd 100644 --- a/docs/diagnostics.md +++ b/docs/diagnostics.md @@ -10,7 +10,7 @@ gate a build, a validation, or a dev rebuild. | Family | Area | | --- | --- | -| `AB30xx` | Skill Markdown parsing (missing or malformed frontmatter). | +| `AB30xx` | Skill documents: Markdown parsing (`AB3000`–`AB3002`: unreadable, missing or malformed frontmatter) and rendered-skill compilation (`AB3003`: module failed to load, `AB3004`: missing/invalid default component or `frontmatter` export, `AB3005`: content outside the supported Markdown element subset). | | `AB40xx` | Plugin metadata and Skill source validation. | | `AB41xx` | Normalized model invariants (unknown targets, duplicate IDs and outputs). | | `AB42xx` | Hook configuration and native hook sources. | @@ -27,7 +27,7 @@ gate a build, a validation, or a dev rebuild. | `AB8xxx` | Development server configuration. | | `AB9xxx` | Eval selection, harnesses, and persisted runs. | -## Migration nudges (`AB4730`–`AB4733`) +## Migration nudges (`AB4730`–`AB4735`) The entry conventions and the framework-owned stdio lifecycle shell (RFC #50) replaced patterns consumers previously wrote by hand. When `validate`, @@ -75,6 +75,25 @@ to it — a confusable state where the file on disk is not what runs. Adopt: drop the explicit `entry`/`command`/`url` so the convention applies. Silence: remove the shadowed file. +### `AB4734` — conventional skill shadowed by explicit `skills` config + +A `skills//SKILL.md` (or rendered `SKILL.tsx`/`SKILL.ts`) directory +exists, but the explicit `skills` configuration does not cover it — the +conventional skill is silently shadowed. When config is silent, every +`skills//` directory ships by convention and this nudge never fires. + +Adopt: remove the explicit `skills` configuration so the convention applies, +or add the directory to `skills`. Silence: remove the directory. + +### `AB4735` — rendered skill source shadowed by hand-authored `SKILL.md` + +A skill directory contains both a hand-authored `SKILL.md` and a rendered +skill source (`SKILL.tsx`/`SKILL.ts`). The authored file wins — an authored +document beats a generated one — so the component module never compiles. + +Adopt: remove `SKILL.md` so the rendered skill compiles at build. Silence: +remove the component module. + ## Development package build (`AB7103`) `agent-bundle dev` rebuilds the framework-owned package build (`dist/` bin diff --git a/docs/entry-conventions.md b/docs/entry-conventions.md index 00988e970..5fe60507f 100644 --- a/docs/entry-conventions.md +++ b/docs/entry-conventions.md @@ -5,6 +5,9 @@ libraries: one `agent-bundle.config.ts`, one CLI, framework-owned entry lifecycles, and a single blessed escape hatch into the bundler. This document is the contract for the package build (`bin` / `lib`), the entry-file conventions, the generated entry shells, and the `tools` escape hatch. +[Framework mode](framework-mode.md) is the one-screen authoring model these +conventions serve: structure in config and conventions, JSX only for +rendering. ## The package build diff --git a/docs/framework-mode.md b/docs/framework-mode.md new file mode 100644 index 000000000..0b5f13bf2 --- /dev/null +++ b/docs/framework-mode.md @@ -0,0 +1,93 @@ +# Framework mode + +Structure lives in `agent-bundle.config.ts` and file conventions. JSX renders. +That is the whole model (RFC #63); RFC #50's entry conventions are the sibling +contract for `bin`/`lib`/MCP entries. + +## What a newcomer must learn + +Three things: + +1. **One directory convention.** Every `skills//SKILL.md` ships as a + Skill. Add a folder and it ships — no declaration anywhere. +2. **One flat config file.** `agent-bundle.config.ts` declares the plugin + identity, targets, and anything a file cannot say for itself: + +```ts +import { defineConfig } from 'agent-bundle'; + +export default defineConfig({ + plugin: { description: '…', name: 'my-plugin', version: '0.1.0' }, + targets: ['portable', 'codex', 'claude'], +}); +``` + +3. **JSX = rendering.** React elements appear only where something is + rendered: MCP/hook results at runtime (`Mcp.Result`, `Hook.Text`), and + skill documents at build time (below). There are no structural JSX + elements — no ``, ``, or ``. + +Entry files follow the same convention-with-fallback trick: `src/cli.ts` is +the package bin, `src/index.ts` the library, `src/mcp/.ts` a +declared server's stdio entry — each applies when the file exists, and +explicit config always wins over a convention (`AB473x` nudges flag the +confusable shadowed states). See `docs/entry-conventions.md`. + +## Applications with operations (when you have a CLI or MCP server) + +`defineRscApplication` declares the runtime identity plus one typed operation +catalog; the conventional entries consume it: + +```ts +// src/application.ts +export const application = defineRscApplication({ + name: 'my-plugin', + operations: [status], + version: '0.1.0', +}); + +// src/cli.ts +export const main = (argv: readonly string[]) => runRscCli(application, argv); + +// src/mcp/runtime.ts +export default () => createRscMcpServer(application, 'runtime'); +``` + +The server's structural declaration (`mcp.servers.runtime: {}`) lives in the +config; the name passed to `createRscMcpServer` only selects which operations +to serve. + +## Rendered skills (power tier, never required) + +A skill whose document is generated: put `SKILL.tsx` (or `SKILL.ts`) in the +skill directory instead of `SKILL.md`. The module default-exports a component +and exports a `frontmatter` record; the build renders the tree to Markdown +and emits the `SKILL.md` every host consumes. + +```tsx +// skills/deploy-checklist/SKILL.tsx +export const frontmatter = { + description: 'Deployment checklist.', + name: 'deploy-checklist', +}; + +export default () => ( + <> +

Deploy checklist

+

Verify each step in order.

+ +); +``` + +The renderer supports a documented element subset (`h1`–`h6`, `p`, +`ul`/`ol`/`li`, `strong`, `em`, `code`, `pre`, `blockquote`, `a`, `hr`, +`br`, fragments) and rejects anything outside it by name — never a silent +approximation. Components may be async, and may import project code, so the +document can be computed from the same sources the plugin ships. A +hand-authored `SKILL.md` in the same directory always wins (`AB4735`). + +## Precedence, said once + +Config wins, conventions fill. Declaring `skills:` in config replaces the +directory convention entirely (`AB4734` flags any directory left uncovered); +the same rule governs `bin`, `lib`, and MCP server entries. diff --git a/examples/audiobook-curator/README.md b/examples/audiobook-curator/README.md index 1aac54d78..6475f709a 100644 --- a/examples/audiobook-curator/README.md +++ b/examples/audiobook-curator/README.md @@ -6,11 +6,12 @@ From the repository root, launch this example with: pnpm example:audiobook ``` -A complete TypeScript recreation of the original `audiobook-curator`, authored -as one React Server Component plugin application. The same typed operation tree -produces a globally installable CLI, one stdio MCP server, one Skill, and native -Claude Code and Codex plugin artifacts. It has no hooks and does not call the old -Python curator. +A complete TypeScript recreation of the original `audiobook-curator`, built in +framework mode: `agent-bundle.config.ts` plus file conventions declare the +structure, and one typed operation catalog produces a globally installable +CLI, one stdio MCP server, one Skill, and native Claude Code and Codex plugin +artifacts. JSX appears only where something is rendered — the MCP result +receipts. It has no hooks and does not call the old Python curator. The package requires Node 22.19+, `ffprobe`, and `ffmpeg`. Optional features call the foreign tools that actually provide the evidence: Audiobook Forge, @@ -49,9 +50,12 @@ dependencies. ## Source layout -- `src/application.tsx` — composition only: merges the feature modules' - defaults and declares the `` tree (Skill, CLI Script, MCP - server, operations). +- `agent-bundle.config.ts` — the structure: plugin identity, targets, the CLI + script, and the MCP server (whose entry is the `src/mcp/curator.ts` + convention). The Skill needs no declaration at all: + `skills/curate-audiobooks/SKILL.md` ships by convention. +- `src/application.ts` — composition only: merges the feature modules' + defaults into one `defineRscApplication` operation catalog. - `src/operations/` — the operation catalog, grouped by workflow stage: `discovery` (inspect/inventory/library-audit/select), `audible` (search/select/cache), `evidence` (acoustic/whisper), `media-mutation` @@ -62,8 +66,8 @@ dependencies. `curator-core.ts`) over the shared `foundation.ts` and `media-process.ts` primitives; `result.tsx` renders every receipt for MCP. - `src/cli.ts` exports `main`; the framework's generated process envelope - turns it into both the bundled `