Skip to content

Commit 92f9346

Browse files
committed
docs: restructure AGENTS.md into a contract over .agents/ pages
1 parent 680da08 commit 92f9346

10 files changed

Lines changed: 348 additions & 228 deletions

.agents/01-positioning.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# 01 - Positioning
2+
3+
## What each layer is
4+
5+
**`devframe`** is the framework-neutral container for one devtool integration, portable across hub UI providers. It packages a single tool - its RPC, its SPA, its diagnostics, its CLI/build/embedded outputs - without caring how it will be displayed. A devframe runs standalone (CLI, static deploy, embedded SPA) just as well as it mounts inside a hub.
6+
7+
**`@devframes/hub`** is the framework-neutral hub layer on top of devframe: multi-devframe orchestration (docks, terminals, messages, commands). It ships no UI - hub UI providers (e.g. `@vitejs/devtools-kit`) provide their own UI over the hub's RPC + shared-state protocol. It does ship a **headless client runtime** (`createDevframeClientRuntime()` from `@devframes/hub/client`): booted in the host page, it assembles the shared `DevframeClientContext` (panel, docks, commands, when) and imports each dock entry's client script (`action` / `custom-render` / iframe `clientScript`) into that page - how a built-in devframe like the a11y inspector runs its page script inside the user app's page. `examples/custom-hub-vite/` is a working ~120-line Vite host demonstrating the protocol end to end.
8+
9+
## Design principles
10+
11+
These enforce the split "devframe provides primitives, the hub provides UX". When in doubt, err toward the primitive.
12+
13+
- **Single-integration scope.** Devframe describes one tool. A feature that only makes sense when multiple tools share a UI - docking, a unified command palette, cross-tool toasts, terminal aggregation - MUST live in a hub package, not in `devframe`.
14+
- **Headless by default.** Core and adapters MUST NOT ship default startup banners, opinionated stdout logging, or default styling. Provide hooks (`onReady`, `cli.configure`, …) and let the application print its own branding. Structured diagnostics via `nostics` are fine ([08](./08-diagnostics.md)); ad-hoc `console.log`s baked into adapters are not.
15+
- **Mount path depends on adapter context.** Given `id: 'foo'`, the default mount path is `/__foo/` for *hosted* adapters (`vite`, `embedded`) and `/` for *standalone* adapters (`cli`, `build`). Authors override via `DevframeDefinition.basePath`. Adapter code that may run standalone MUST NOT hardcode a mount path.
16+
- **SPAs own their basePath at runtime.** SPAs are built with relative asset paths (`vite.base: './'`) and discover the effective base in the browser from the executing script's location / `document.baseURI`. `createBuild` copies SPA output verbatim - no HTML rewriting, no build-time `--base` injection. The RPC client (`connectDevframe`) resolves `.connection.json` relative to the runtime base automatically.
17+
- **CLI flags compose from both sides.** The `cac` instance backing `createCac` is exposed to the `DevframeDefinition` (`cli.configure(cli)`) for capabilities the tool contributes, and to the `createCac` caller for flags added at final assembly. Parsed flag values are forwarded to `setup(ctx, { flags })`. `createCac` itself MUST NOT hardcode domain-specific flags.

.agents/02-terminology.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# 02 - Terminology
2+
3+
The docs' canonical vocabulary lives in [`docs/content/8.references/1.terms.md`](../docs/content/8.references/1.terms.md) - one name per concept. Every docs, README, and comment edit MUST follow it.
4+
5+
- Bare `client`, `host`, `server`, `agent`, `plugin`, `embedded`, or `standalone` MUST NOT appear in prose. Use a fixed compound from the terms page or a code-formatted API/package name. One exception: the directional `client → server` arrows in the RPC/events reference tables.
6+
- The tool is **"a devframe"** - never "integration", "frame", or "app". The ready-to-run `@devframes/plugin-*` packages are **built-in devframes**: devframe has no plugin concept; the `plugin-` npm prefix only sets those packages apart from core packages. "Vite plugin" stays for the bundler mechanism, and RPC ids keep the literal `devframes:plugin:<slug>:` namespace because it mirrors the package names on the wire.
7+
- **host framework** is the environment a devframe or hub mounts into (a Vite dev server, a Next.js app, a Hono server); named forms like "the Vite host" are fine. **host page** is the browser document where the client runtime boots; **user app** is the application being developed and inspected.
8+
- A devframe's two halves are the **node side** and the **browser side**.
9+
- Browser-side terms: **client runtime** (`createDevframeClientRuntime()`), **client context**, **client script**, **page script** (a devframe's script in the user app's page - never "agent"; **coding agent** is the only agent), **RPC client** (`connectDevframe()`), **SPA**, **panel** (a devframe's SPA as a rendered surface), **surface** (any rendered browser view - say "API", not "API surface").
10+
- Hub terms: **hub UI provider** (a hub UI implementation - never "shell" or bare "viewer"; "external viewer" stays for cross-origin surfaces in the security docs), **dock entry** / **dock rail** / **dock panel**, **mounted devframe** (never "frame").
11+
- The three communication paths: **RPC** (browser side ↔ node side), the **client context** (client scripts ↔ client runtime), and the **in-page channel** (page script ↔ panel, same-origin in-browser).
12+
- Storage scopes: **workspace scope** (committable, per-repo), **project scope** (per-checkout), **global scope** (per-user). The project scope MUST NOT be described as "per-workspace".
13+
- **framework kits** are `@devframes/vite` / `@devframes/nuxt` / `@devframes/next`; refer to external products by their full names (`@vitejs/devtools-kit`, `@nuxt/devtools`).
14+
- Qualify the rest: embedded/standalone only as attached adjectives (embedded adapter, standalone SPA), sessions (terminal / MCP / trust session), entries (dock entry / entry point / browser entry), bridges (RPC bridge).

.agents/03-stack-and-commands.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# 03 - Stack, layout, and commands
2+
3+
## Stack
4+
5+
ESM TypeScript library. Bundled with `tsdown`. Tested with `vitest`. pnpm workspaces with catalog dependencies (`pnpm-workspace.yaml`); workspace globs reserve `playground`, `docs`, `packages/*`, `examples/*` for future additions.
6+
7+
Per-package layout:
8+
9+
- `src/` - library code; entry `src/index.ts`
10+
- `test/` - vitest specs; API snapshots via `tsnapi` under `test/__snapshots__/`
11+
- `dist/` - `tsdown` build output (shipped to the npm tarball via `files`)
12+
13+
## Commands
14+
15+
```sh
16+
pnpm install # requires pnpm@11.x
17+
pnpm build # tsdown
18+
pnpm dev # tsdown --watch
19+
pnpm test # pnpm build && vitest (api snapshot guards against stale dist)
20+
pnpm typecheck # turbo run typecheck (per-package tsc --noEmit)
21+
pnpm lint --fix # ESLint via @antfu/eslint-config
22+
pnpm knip # unused files/dependencies/exports across every workspace
23+
pnpm start # tsx src/index.ts
24+
```
25+
26+
Before opening a PR, all five gates MUST pass:
27+
28+
```sh
29+
pnpm lint && pnpm knip && pnpm test && pnpm typecheck && pnpm build
30+
```
31+
32+
Commits and PR titles MUST follow Conventional Commits (`feat:`, `fix:`, …).
33+
34+
## Testing
35+
36+
The `pnpm test` script intentionally runs `build` first so `tsnapi` snapshots compare against fresh `dist/`. `tsdown-stale-guard` enforces this in `test/api-snapshot.test.ts`.
37+
38+
## Typechecking
39+
40+
`pnpm typecheck` fans out through Turbo: every workspace package MUST own a `"typecheck": "tsc --noEmit"` script and its own `tsconfig.json` (extending `tsconfig.base.json` with an explicit `include`). Cross-package imports resolve to source through the `paths` aliases in `tsconfig.base.json`, so no prior build is needed. A new package under `packages/*` or `plugins/*` joins the fan-out the moment it ships that script - always add one so type errors can't be silently skipped.
41+
42+
`scripts/verify-typecheck-coverage.ts` runs first and fails the command (and CI, which just runs `pnpm typecheck`) if any workspace package has a `tsconfig.json` but no `typecheck` script. A package that genuinely can't typecheck yet needs a documented exception in that script, not a missing script.
43+
44+
## Generated artifacts under `src/`
45+
46+
Ahead-of-time build artifacts that live under `src/` - the shadow-root stylesheets in `packages/hub-ui/src/client/.generated/` and `packages/json-render-ui/src/.generated/` - are **generated, not committed** (`.generated` is gitignored). Each owning package builds its own with `pnpm run build:css`; three things guarantee the file is on disk before anything imports it: the root `postinstall` runs `turbo run build:css`, the Turbo `typecheck` task depends on both `build:css` tasks, and each package's `build` script chains `build:css` first. A new generated-under-`src` artifact MUST follow the same shape - its own build script, declared `outputs` in `turbo.json`, and a `typecheck` dependency - and MUST NOT be checked in: a minified single-line blob conflicts on every concurrent edit.
47+
48+
## `starter/`
49+
50+
The top-level, self-contained template for creating a new devframe (Vanilla TS, Vite SPA, playgrounds, tests). It uses real versions in its `package.json` (no catalogs, no `workspace:*`) so it is copy-paste ready for users; pnpm links its `devframe`/`@devframes/*` dependencies to the local workspace copies during development. When `bumpp -r` bumps the repo versions, `bumpp.config.ts` runs `scripts/sync-starter-version.ts` to update the starter's dependencies to match.
51+
52+
## knip
53+
54+
`pnpm knip` finds unused files, dependencies, and exports across every workspace (config in `knip.jsonc`), running against source directly - no prior build needed. Most workspaces need no configuration; `knip.jsonc` only carries per-workspace overrides for what knip's defaults can't infer:
55+
56+
- **Non-`index.ts` `exports` subpaths.** knip's package.json→`dist``src` source mapping needs a workspace `tsconfig.json` `outDir`, which conflicts with this repo's cross-workspace `src/*.ts` imports, so multi-entry packages list their `exports`-mapped entry files explicitly - keep that list in sync with each `tsdown.config.ts`.
57+
- **Config files knip's plugins don't discover** in a nested location (a Next.js app rooted below the workspace root, `storybook-solidjs-vite` not matching the Storybook plugin trigger).
58+
- **Dependencies referenced dynamically** outside the static import graph (icon collections consumed by UnoCSS at build time, built-in devframe packages loaded via a runtime `import()` string).
59+
60+
Prefer fixing the underlying gap or a scoped `ignoreDependencies`/`entry` override over a blanket `ignore`.

.agents/04-conventions.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# 04 - Code conventions
2+
3+
## RPC
4+
5+
RPC functions MUST use `defineRpcFunction`. Built-in devframes MUST namespace IDs `devframes:plugin:<slug>:<fn-name>` - the literal `plugin:` token mirrors the `@devframes/plugin-<slug>` package name on the wire; it is npm namespacing, not a concept.
6+
7+
## Event names come from the centralized maps
8+
9+
Every event, broadcast, shared-state key, and channel name lives in one of two source-of-truth maps: `DEVFRAME_EVENTS` (`packages/devframe/src/events.ts`, re-exported from `devframe/constants`) for the core runtime, and `HUB_EVENTS` (`packages/hub/src/events.ts`, re-exported from `@devframes/hub/constants`) for the hub. Call sites (`.events.emit`/`.on`, `rpc.broadcast({ method })`, `sharedState.get(key)`, `defineHubRpcFunction({ name })`, `rpc.call`) MUST reference `DEVFRAME_EVENTS.*` / `HUB_EVENTS.*`, never a re-typed string literal.
10+
11+
The two maps and the [`docs/content/8.references/3.events.md`](../docs/content/8.references/3.events.md) Events Reference stay in lockstep: adding, renaming, or removing a name means editing the map **and** that page in the same change; every name in the maps appears in the tables, and vice versa.
12+
13+
Permitted literals: unavoidable type-position keys (the `EventEmitter<…>` maps in `types/*` and the `DevframeRpcClientFunctions`/`DevframeRpcServerFunctions` augmentations), which mirror the maps; and a package that deliberately avoids a hub dependency (e.g. `@devframes/plugin-terminals`, which models the hub bridge structurally) keeps a local literal rather than importing `HUB_EVENTS`.
14+
15+
## Validator neutrality
16+
17+
`devframe` and every `@devframes/*` package MUST NOT carry a schema validator (`valibot`, `zod`, `arktype`, …) in their runtime `dependencies`. `args`/`returns`/flag schemas are typed against [Standard Schema](https://standardschema.dev/) (`@standard-schema/spec`, types-only); first-party code that needs to author a schema uses the built-in zero-dep `devframe/utils/simple-schema` builder (deliberately minimal - not a general validator). JSON-schema conversion uses each schema's own Standard JSON Schema converter (`~standard.jsonSchema`, implemented by e.g. zod 4) when present and degrades to a permissive object otherwise - no converter library, no vendor dependency.
18+
19+
Docs, by contrast, SHOULD point *users* at a real validator for their own integrations - recommend **valibot** (lightest) or **zod** (worth reusing if they already pull it via the JSON-render or MCP integrations).
20+
21+
## Imports, state, dependencies
22+
23+
- Shared state goes through `devframe/utils/shared-state`; values MUST stay serializable.
24+
- Utility imports use the package-path form `devframe/utils/*`, never relative `../utils/*`.
25+
- Dependencies go through the pnpm catalogs in `pnpm-workspace.yaml` (`cli`, `inlined`, `testing`, `types`) - add to a catalog and reference as `catalog:<name>`; versions MUST NOT be pinned in a `package.json`.
26+
27+
## Default exports are factories
28+
29+
A built-in devframe's default export MUST be its `create<X>Devframe` factory, never a pre-built instance. `export default createXDevframe()` (or the two-line equivalent) eagerly constructs a `DevframeDefinition` at import time whether or not any consumer wants that exact zero-config shape; a consumer that needs its own options (an id override, a data directory, …) pays for a second, discarded instance. Alias the factory itself - `export default createXDevframe` - so importing costs nothing and every consumer calls it to get their own instance: `import createA11yDevframe from '@devframes/plugin-a11y'` then `createA11yDevframe(options)`.

.agents/05-framework-kits.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# 05 - Framework kits: two scopes, one shape
2+
3+
The framework kits - `@devframes/vite`, `@devframes/nuxt`, `@devframes/next` - each split their surface into two clearly-scoped subpaths, because a consumer is always doing one of two distinct jobs. All three kits MUST stay parallel.
4+
5+
## `.../single` - author one devframe
6+
7+
Build & dev-serve a single devframe's SPA with that tool. Vite: the `devframeVitePlugin` / `devframeViteBridge` / `devframeVite` plugins. Next: `withDevframe` + `createDevframeNextHandler`, with its React client at `.../single/client`. Nuxt: the Nuxt module (registered as `modules: ['@devframes/nuxt/single']`).
8+
9+
## `.../hub` - stand up devtools
10+
11+
Mount a whole `@devframes/hub` (many devframes) inside that tool. Wraps `initHub`, defaults the UI slot to `@devframes/hub-ui`'s `createUi()` (overridable via `ui`, or `ui: false` for headless), and ships a browser client helper at `.../hub/client` (a thin, lifecycle-managing wrapper over `@devframes/hub/client`'s `createDevframeClientRuntime`).
12+
13+
`@devframes/hub` and `@devframes/hub-ui` are **optional peers** of these packages; `hub-ui` MUST be loaded lazily (a bundler-ignored dynamic `import()` in the Next hub) so it stays optional and its `import.meta.url` asset lookups resolve at request time.
14+
15+
## Rules
16+
17+
- The bare root (`.`) MUST throw a helpful error pointing at the two subpaths - real code never lives on it.
18+
- Vite and Nuxt already have native hub UI providers (`@vitejs/devtools-kit`, `@nuxt/devtools`), so `@devframes/vite/hub` and `@devframes/nuxt/hub` still work but emit a one-time `console.warn` recommending those (silenced with `{ quiet: true }`). `@devframes/next/hub` has no native counterpart and warns nothing.
19+
20+
## How the examples consume the kits
21+
22+
The **full hub examples** (`examples/custom-hub-vite`, `examples/custom-hub-next`) consume `.../hub` on the node side but hand-roll their own hub UI provider against `@devframes/hub/client` with `ui: false` - that hand-rolled hub UI provider is the whole point of those reference hosts ([07](./07-hub-examples.md)). The **minimal** ones (`examples/hub-vite`, `examples/hub-next`, `examples/hub-deno`, `examples/hub-fastify`, `examples/hub-hono`, `examples/hub-nitro`, `examples/hub-rsbuild`, `examples/hub-sveltekit`) consume `.../hub` with the default `@devframes/hub-ui` and inject its `embedded.js`, needing no browser-side code.

0 commit comments

Comments
 (0)