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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .changeset/framework-mode-agent-bundle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
"agent-bundle": minor
---

Framework mode (RFC #63), compiler side. The `skills/<name>/` 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/<name>/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.
19 changes: 19 additions & 0 deletions .changeset/framework-mode-rsc-runtime.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<server-id>.ts`) emit executable `dist/bin/<name>.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/<server-id>.ts`) emit executable `dist/bin/<name>.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/<name>/SKILL.md` ships with no declaration at all), JSX only where something is rendered.

## Commands

Expand Down
23 changes: 21 additions & 2 deletions docs/diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand All @@ -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`,
Expand Down Expand Up @@ -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/<name>/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/<name>/` 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
Expand Down
3 changes: 3 additions & 0 deletions docs/entry-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
93 changes: 93 additions & 0 deletions docs/framework-mode.md
Original file line number Diff line number Diff line change
@@ -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/<name>/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 `<AgentBundle>`, `<Skill>`, or `<McpServer>`.

Entry files follow the same convention-with-fallback trick: `src/cli.ts` is
the package bin, `src/index.ts` the library, `src/mcp/<server-id>.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 () => (
<>
<h1>Deploy checklist</h1>
<p>Verify each step <strong>in order</strong>.</p>
</>
);
```

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.
32 changes: 18 additions & 14 deletions examples/audiobook-curator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -49,9 +50,12 @@ dependencies.

## Source layout

- `src/application.tsx` — composition only: merges the feature modules'
defaults and declares the `<AgentBundle>` 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`
Expand All @@ -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 `<Script>` artifact entry and the npm bin.
`src/mcp-server.ts` default-exports a server factory served under the
turns it into both the bundled script artifact entry and the npm bin.
`src/mcp/curator.ts` default-exports a server factory served under the
framework's stdio lifecycle shell. No hand-written entry shims remain.

## Complete workflow
Expand Down Expand Up @@ -125,8 +129,8 @@ The completion contract and real-volume checklist are in
## Maintainer notes

This example is the reference consumer of the framework-owned package build
("one config, agent-bundle owns the build"): `agent-bundle.config.ts` is a
pure pass-through of the RSC application's config, and the `src/cli.ts` /
`src/index.ts` conventions provide the npm bin and library outputs under
`dist/`. See [`docs/entry-conventions.md`](../../docs/entry-conventions.md)
("one config, agent-bundle owns the build"): `agent-bundle.config.ts` declares
the structure directly, and the `src/cli.ts` / `src/index.ts` conventions
provide the npm bin and library outputs under `dist/`. See
[`docs/entry-conventions.md`](../../docs/entry-conventions.md)
for the contract.
29 changes: 27 additions & 2 deletions examples/audiobook-curator/agent-bundle.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
import { audiobookCuratorApplication } from './src/application.tsx';
import { defineConfig } from 'agent-bundle';

export default audiobookCuratorApplication.config;
export default defineConfig({
marketplace: true,
mcp: {
servers: {
// No `entry` needed: the conventional stdio entry `src/mcp/curator.ts`
// supplies it, and its default-exported factory runs under the
// framework lifecycle shell.
curator: {},
},
},
plugin: {
description:
'Complete plan-first audiobook inventory, matching, conversion, repair, and integrity audit.',
name: 'audiobook-curator',
version: '1.0.0',
},
runtime: { node: '22.19.0' },
// `src/cli.ts` is the package bin by convention; declaring it as a script
// also ships it inside every host artifact.
scripts: {
'audiobook-curator': './src/cli.ts',
},
// No `skills` field needed: `skills/curate-audiobooks/SKILL.md` is
// discovered by convention.
targets: ['claude', 'codex'],
});
2 changes: 1 addition & 1 deletion examples/audiobook-curator/docs/parity-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ adapter coverage.
| Sources are immutable; mutation is plan-only without explicit `apply` | mutation foundation | real/synthetic before-and-after hashes |
| No shell execution; bounded output; caller cancellation; no local media deadline | capability/process foundation | child-process tests |
| Natural ordering, Unicode-safe identity, safe filenames without apostrophes | domain text foundation | ported pure tests |
| Claude and Codex derive Skill, script, and MCP from one TSX application | `src/application.tsx` | artifact and installed-host tests |
| Claude and Codex derive Skill, script, and MCP from one config plus conventions | `agent-bundle.config.ts`, `src/application.ts` | artifact and installed-host tests |

## Operations

Expand Down
2 changes: 1 addition & 1 deletion examples/audiobook-curator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@agent-bundle-example/audiobook-curator",
"version": "1.0.0",
"private": true,
"description": "A complete RSC-authored Claude and Codex audiobook-curation plugin, CLI, Skill, and MCP server.",
"description": "A complete framework-mode Claude and Codex audiobook-curation plugin, CLI, Skill, and MCP server.",
"type": "module",
"engines": {
"node": ">=22.19.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
/**
* The audiobook-curator application: one `defineRscAgentBundle` tree that
* composes the Skill, the CLI Script, the MCP server, and the operation
* catalog. The operations themselves live in feature modules under
* `./operations/`; this file only merges their defaults and declares the
* bundle.
* The audiobook-curator application: runtime identity plus the operation
* catalog. Structure — targets, the Skill, the CLI script, the MCP server
* lives in `agent-bundle.config.ts` and file conventions; the operations
* themselves live in feature modules under `./operations/`, and this file
* only merges their defaults.
*/
import {
AgentBundle,
McpServer,
Operation,
Script,
Skill,
defineRscAgentBundle,
} from '@agent-bundle/rsc-runtime/plugin';
import React from 'react';
import { defineRscApplication } from '@agent-bundle/rsc-runtime/plugin';

import {
audibleOperations,
Expand Down Expand Up @@ -62,32 +54,18 @@ const operationDefinitions = (operations: Required<AudiobookCuratorOperations>)

export const createAudiobookCuratorApplication = (
options: { readonly operations?: AudiobookCuratorOperations } = {},
) => {
const definitions = operationDefinitions({
) => defineRscApplication({
description: 'Complete plan-first audiobook inventory, matching, conversion, repair, and integrity audit.',
name: 'audiobook-curator',
operations: operationDefinitions({
...defaultAudibleOperations,
...defaultDiscoveryOperations,
...defaultEvidenceOperations,
...defaultMediaMutationOperations,
...defaultOutputOperations,
...options.operations,
});
return defineRscAgentBundle(
<AgentBundle
description="Complete plan-first audiobook inventory, matching, conversion, repair, and integrity audit."
marketplace
name="audiobook-curator"
node="22.19.0"
targets={['claude', 'codex']}
version="1.0.0"
>
<Skill source="./skills/curate-audiobooks" />
{/* The framework process envelope invokes the exported `main`; no
hand-written self-executing entry file is needed. */}
<Script entry="./src/cli.ts" name="audiobook-curator" />
<McpServer entry="./src/mcp-server.ts" name="curator" />
{definitions.map((definition) => <Operation definition={definition} key={definition.id} />)}
</AgentBundle>,
);
};
}),
version: '1.0.0',
});

export const audiobookCuratorApplication = createAudiobookCuratorApplication();
12 changes: 0 additions & 12 deletions examples/audiobook-curator/src/mcp-server.ts

This file was deleted.

13 changes: 13 additions & 0 deletions examples/audiobook-curator/src/mcp/curator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createRscMcpServer } from '@agent-bundle/rsc-runtime/plugin';

import { audiobookCuratorApplication } from '../application.js';

export const createAudiobookCuratorServer = () => createRscMcpServer(audiobookCuratorApplication, 'curator');

/**
* Default-exported server factory at the conventional `src/mcp/curator.ts`
* entry: `agent-bundle build` detects it and wraps it in the framework stdio
* lifecycle shell (console-to-stderr guard, SIGINT/SIGTERM handling,
* stdin-EOF exit, bounded shutdown, heartbeat).
*/
export default createAudiobookCuratorServer;
Loading
Loading