feat: release design system engine, studio and component contracts
This commit is contained in:
1 parent
ff1065fb27
commit
3943b946d6
693 files changed
+175546
-3894
No files matched your search
@@ -9,3 +9,7 @@ bunx wrnexus-mcp --root=.
|
||||
It exposes current routes, components with props/events, database schema files, compiler diagnostics,
|
||||
runtime errors, dev-server health, framework documentation and installed packages. Files are resolved
|
||||
inside the configured application root and returned as bounded structured JSON.
|
||||
|
||||
## Packaging, shared designs and recovery
|
||||
|
||||
See [RELEASE.md](RELEASE.md) for installed Studio/MCP usage, deterministic shared design packages, explicit upgrades, storage migration and the local release workflow. Existing APIs and default component behavior remain available.
|
||||
@@ -0,0 +1,72 @@
|
||||
# Design release and upgrade guide
|
||||
|
||||
These packages support Bun 1.3 or newer. The design engine and Studio root/upgrade APIs are browser safe. Studio's server, CLI and agent service are local authoring tools; an application only needs the engine, theme adapter and UI components.
|
||||
|
||||
## Installed use
|
||||
|
||||
Install `@wrnexus/design-system` for the data engine. Add `@wrnexus/design-studio` for authoring; the installed `wrnexus-design-studio --app-root EXISTING_APP` binary binds to loopback port 4320 (`DESIGN_STUDIO_PORT` overrides it). Without `--app-root`, only exports are enabled. No AI key is needed. Existing `createStudioHandler` programmatic integration remains available from `@wrnexus/design-studio/server`.
|
||||
|
||||
Install `@wrnexus/mcp` for the existing eight framework tools. Studio is an **optional peer**, so a normal MCP-only installation does not install or load Studio. Install compatible Studio explicitly and use `wrnexus-mcp --root=EXISTING_APP --design` for inspection or `--design-write` for draft and bundle mutations. Missing Studio produces `WRN-MCP-DESIGN-INSTALL` when a design operation is invoked. The default server still exposes eight tools.
|
||||
|
||||
Studio packages ship compiled JS/declarations, component metadata through declared dependencies, and a prebuilt browser client. Published packages do not require a sibling source checkout. Application configuration and routes are never rewritten implicitly.
|
||||
|
||||
## Share a pinned design
|
||||
|
||||
```ts
|
||||
import { createDesignPackage, readDesignPackage } from "@wrnexus/design-system/package";
|
||||
|
||||
// Run after validating/reviewing your self-contained canonical design:
|
||||
const artifact = await createDesignPackage(system, "Reviewed brand release");
|
||||
const source = JSON.stringify(artifact);
|
||||
// Store this JSON with your application or distribute it in your own versioned package.
|
||||
const verified = await readDesignPackage(source);
|
||||
const theme = { designSystem: { system: verified.package.system } };
|
||||
```
|
||||
|
||||
Assign that theme through the existing app configuration. Alternatively use `compileDesignSystem` to generate standalone CSS; avoid loading both integrations. Explicit app tokens and instance props retain precedence.
|
||||
|
||||
The envelope has format version 1, contract version 1, the canonical schema-v1 design and a deterministic SHA-256 content digest. Object key order does not change the pin. The digest detects changed content; it is not a publisher signature. Use a trusted distribution source and pin the artifact together with engine/UI package versions and the app lockfile. Version-1 contracts do not replace visual regression review after dependency updates.
|
||||
|
||||
Bundles are self-contained: unresolved external inheritance is rejected. Resolve and flatten your inherited specification before sharing it. Names and types remain canonical; there is no automatic token renaming. Package source is capped at 2 MB and release notes at 2,000 characters.
|
||||
|
||||
## Review an upgrade
|
||||
|
||||
```ts
|
||||
import { planDesignUpgrade, applyDesignUpgrade } from "@wrnexus/design-studio/upgrades";
|
||||
|
||||
const plan = await planDesignUpgrade(session, incomingJson);
|
||||
// Review plan.changes, diagnostics, migrations and the matching page previews.
|
||||
if (plan.canApply) await applyDesignUpgrade(session, incomingJson, plan.baseRevision);
|
||||
// Existing session.undo()/redo() and autosave still apply.
|
||||
```
|
||||
|
||||
Planning does not mutate the session. Adoption rereads and verifies the package, checks system identity and the exact current design/lock revision, then uses the existing history mechanism. Invalid source must be resolved first. Locked decisions and their effective alias/recipe dependencies remain protected. Another design id requires an explicit import. Version numbers do not trigger automatic downloads or updates: a supported version with the same id may be adopted explicitly after review, including a deliberate rollback.
|
||||
|
||||
MCP provides `wrnexus_design_package` to export a verified package, `wrnexus_design_upgrade_plan` to review incoming `source`, and `wrnexus_design_upgrade` with `source` plus the latest `baseRevision` to adopt it. Existing draft/application revisions remain separate. Application changes still require `baseApplicationRevision`; exporting or upgrading a draft never deploys it.
|
||||
|
||||
## Migration and recovery
|
||||
|
||||
- Existing canonical schema-v1 JSON can be wrapped through `readDesignPackage`; it reports `canonical-v1-to-package-v1`. Existing canonical/legacy/token imports continue to use the existing explicit import APIs.
|
||||
- Old Studio browser drafts and agent state without a storage version remain readable. Successful subsequent saves add storage version 1, retaining source history, invalid raw edits, locks and saved pages.
|
||||
- Unknown package formats or contract versions fail closed. Unknown/faulty browser storage is kept byte-for-byte: `session.recoverySource` exposes the saved bytes and automatic writes stay blocked until storage is deliberately reset after backup. Keep the original browser storage key `wrnexus.design-studio.v1`; do not clear it before exporting recovery data. If you are using the UI, back up that local-storage value before resetting it; new package/upgrade APIs are programmatic, not additional Studio controls.
|
||||
- Unknown agent-state versions also fail closed; `STATE.json` is not rewritten. Back up `.wrnexus/design-studio` before changing authoring versions. Older software cannot promise to read newer formats; retain a supported tool version alongside the backup.
|
||||
- Agent state lives under the configured app's `.wrnexus/design-studio/agent`; browser local storage remains separate. Application revisions are immutable bundles selected by `CURRENT.json`. Existing application pointers and bundles need no migration.
|
||||
- On a crash-held `LOCK` or `APPLY.lock`, stop all writers and back up the managed directory before removing the empty lock directory. Locks are never stolen automatically. Do not edit saved state with active writers.
|
||||
|
||||
Upgrading Studio does not apply a theme, alter app configuration or rewrite route files. Exported pages and generated theme bundles still require explicit application integration. Preview execution remains limited to trusted shipped components; submitted arbitrary source is only statically checked.
|
||||
|
||||
## Local release workflow (maintainers)
|
||||
|
||||
From the framework repository:
|
||||
|
||||
```sh
|
||||
bun run design:release:plan
|
||||
bun run design:release:stage
|
||||
bun run design:release:check
|
||||
# Full repository production checks followed by installed-consumer checks:
|
||||
bun run design:release:gate
|
||||
```
|
||||
|
||||
Staging builds the complete declared local dependency closure under `.publish-design`, preserving the standard `.publish` staging area. It validates assets/declarations, rewrites workspace ranges and preserves optional-peer metadata. Checks create real npm tarballs and install them into temporary consumers with lifecycle scripts disabled. They exercise two independent apps, shared pins, upgrades/recovery, browser-safe exports, prebuilt Studio assets and CLI, real component previews, installed types, MCP stdio and a separate MCP-only installation.
|
||||
|
||||
These commands never authenticate, bump source versions, publish or deploy. Output reports and tarballs are under `output/design-system-phase-7`. A successful packaging report is **not** a publication approval or a passing repository-wide production gate. Before publishing, confirm unused package versions against the target registry, run the existing version/release process intentionally, restage, rerun all production and consumer gates, and review the package integrity manifest. The default staged access remains restricted. Do not automatically switch package access or publish dependencies to a public registry.
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/mcp",
|
||||
"version": "0.8.8",
|
||||
"version": "0.8.9",
|
||||
"type": "module",
|
||||
"description": "Model Context Protocol server exposing WRNexus application and framework context.",
|
||||
"main": "src/index.ts",
|
||||
@@ -10,5 +10,16 @@
|
||||
},
|
||||
"bin": {
|
||||
"wrnexus-mcp": "src/stdio.ts"
|
||||
}
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@wrnexus/design-studio": "workspace:*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@wrnexus/design-studio": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"RELEASE.md"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import type { McpTool } from "./index.ts";
|
||||
const string = { type: "string" };
|
||||
const fields: Record<string, Record<string, unknown>> = {
|
||||
package: { notes: string },
|
||||
upgrade_plan: { source: string },
|
||||
upgrade: { source: string },
|
||||
inspect: {},
|
||||
capabilities: {},
|
||||
context: { components: { type: "array", items: string, maxItems: 12 } },
|
||||
create: { source: string },
|
||||
propose: {
|
||||
source: string,
|
||||
patches: {
|
||||
type: "array",
|
||||
maxItems: 48,
|
||||
items: {
|
||||
type: "object",
|
||||
properties: { path: string, value: {} },
|
||||
required: ["path", "value"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
locks: { locks: { type: "array", items: string, maxItems: 128 } },
|
||||
page: {
|
||||
page: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: string,
|
||||
title: string,
|
||||
components: {
|
||||
type: "array",
|
||||
minItems: 1,
|
||||
maxItems: 12,
|
||||
items: {
|
||||
type: "object",
|
||||
properties: { component: string, props: { type: "object" } },
|
||||
required: ["component"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ["id", "title", "components"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
validate: {},
|
||||
preview: { pageId: string },
|
||||
diff: {},
|
||||
apply: { baseApplicationRevision: string },
|
||||
revert: {
|
||||
target: { type: "string", enum: ["draft", "checkpoint", "application"] },
|
||||
baseApplicationRevision: string,
|
||||
},
|
||||
export: {},
|
||||
conformance: {
|
||||
source: string,
|
||||
exceptions: {
|
||||
type: "array",
|
||||
maxItems: 64,
|
||||
items: {
|
||||
type: "object",
|
||||
properties: { code: string, path: string, reason: string },
|
||||
required: ["code", "path", "reason"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const descriptions: Record<string, string> = {
|
||||
package:
|
||||
"Export a verified self-contained design package with a content digest. No filesystem or draft mutation.",
|
||||
upgrade_plan:
|
||||
"Review a shared package upgrade, including integrity, identity, locks and migration diagnostics, without changing the draft.",
|
||||
upgrade:
|
||||
"Adopt a verified shared design package as a recoverable history entry; draft revision and decision locks are rechecked.",
|
||||
inspect: "Inspect recoverable source, valid preview, locks, pages and revisions.",
|
||||
capabilities: "List contracted components, operations and explicit integration limits.",
|
||||
context: "Get resolved tokens, selected component APIs, parts, constraints and generation rules.",
|
||||
create:
|
||||
"Create a recoverable design draft from optional canonical source; invalid source remains recoverable.",
|
||||
propose:
|
||||
"Propose canonical source or token/recipe JSON-pointer patches respecting decision locks.",
|
||||
locks: "Set explicit decision locks for future edits.",
|
||||
page: "Save a bounded literal page specification composed of contracted components; returns generated .wrn source.",
|
||||
validate: "Validate draft and saved page specifications.",
|
||||
preview:
|
||||
"Render a saved page through trusted shipped components. Returns CSP-protected static HTML.",
|
||||
diff: "Review design changes against the applied baseline and affected page ids.",
|
||||
apply:
|
||||
"Save an application design bundle after checking draft and application revisions. Explicit app integration still required.",
|
||||
revert:
|
||||
"Undo a draft edit, swap the previous draft checkpoint, or revert the application bundle pointer.",
|
||||
export:
|
||||
"Export raw draft recovery data, valid design artifacts and generated page sources without writing routes.",
|
||||
conformance:
|
||||
"Statically check .wrn source for unknown APIs, invented variants and token bypasses. Warnings need exact reviewed exceptions.",
|
||||
};
|
||||
export const DESIGN_WRITES = new Set([
|
||||
"create",
|
||||
"propose",
|
||||
"locks",
|
||||
"page",
|
||||
"apply",
|
||||
"revert",
|
||||
"upgrade",
|
||||
]);
|
||||
export function designTools(writable: boolean): McpTool[] {
|
||||
return Object.entries(fields)
|
||||
.filter(([name]) => writable || !DESIGN_WRITES.has(name))
|
||||
.map(([name, properties]) => ({
|
||||
name: "wrnexus_design_" + name,
|
||||
description: descriptions[name]!,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
...properties,
|
||||
...(["context", "preview", "export", "apply"].includes(name)
|
||||
? { mode: string, density: string }
|
||||
: {}),
|
||||
...(DESIGN_WRITES.has(name) ? { baseRevision: string } : {}),
|
||||
},
|
||||
required: [
|
||||
...(DESIGN_WRITES.has(name) ? ["baseRevision"] : []),
|
||||
...({
|
||||
upgrade_plan: ["source"],
|
||||
upgrade: ["source"],
|
||||
page: ["page"],
|
||||
preview: ["pageId"],
|
||||
locks: ["locks"],
|
||||
apply: ["baseApplicationRevision"],
|
||||
conformance: ["source"],
|
||||
}[name] ?? []),
|
||||
],
|
||||
additionalProperties: false,
|
||||
},
|
||||
}));
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { designTools } from "./design-tools.ts";
|
||||
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
||||
import { extname, join, relative, resolve } from "node:path";
|
||||
|
||||
@@ -8,9 +9,13 @@ export interface McpTool {
|
||||
type: "object";
|
||||
properties?: Record<string, unknown>;
|
||||
additionalProperties?: boolean;
|
||||
required?: string[];
|
||||
};
|
||||
}
|
||||
export interface McpServerOptions {
|
||||
/** Opt-in local workspace design operations. */
|
||||
design?: boolean;
|
||||
designWrite?: boolean;
|
||||
maxFiles?: number;
|
||||
maxFileBytes?: number;
|
||||
fetch?: typeof fetch;
|
||||
@@ -138,6 +143,8 @@ export function createFrameworkMcpServer(
|
||||
options: McpServerOptions = {},
|
||||
): McpServer {
|
||||
const root = resolve(appRoot);
|
||||
const toolSet = [...TOOLS, ...(options.design ? designTools(options.designWrite === true) : [])];
|
||||
let design: import("@wrnexus/design-studio/agents").DesignAgentService | undefined;
|
||||
const limits = {
|
||||
maxFiles: options.maxFiles ?? 200,
|
||||
maxFileBytes: options.maxFileBytes ?? 128 * 1024,
|
||||
@@ -145,6 +152,26 @@ export function createFrameworkMcpServer(
|
||||
const files = (directories: string[], extensions?: string[]) =>
|
||||
walk(root, directories, limits, extensions);
|
||||
const call = async (name: string, args: Record<string, unknown> = {}): Promise<unknown> => {
|
||||
if (name.startsWith("wrnexus_design_") && toolSet.some((tool) => tool.name === name)) {
|
||||
if (!design) {
|
||||
const { DesignAgentService } = await import("@wrnexus/design-studio/agents").catch(
|
||||
(error: unknown) => {
|
||||
if (
|
||||
error &&
|
||||
typeof error === "object" &&
|
||||
"code" in error &&
|
||||
["MODULE_NOT_FOUND", "ERR_MODULE_NOT_FOUND"].includes(String(error.code))
|
||||
)
|
||||
throw Error(
|
||||
"WRN-MCP-DESIGN-INSTALL: install a compatible @wrnexus/design-studio package to enable design tools.",
|
||||
);
|
||||
throw error;
|
||||
},
|
||||
);
|
||||
design = new DesignAgentService(root, options.designWrite === true);
|
||||
}
|
||||
return design.call(name.slice("wrnexus_design_".length), args);
|
||||
}
|
||||
if (name === "wrnexus_routes")
|
||||
return files(["app/pages", "app/api"], [".wrn", ".ts", ".tsx", ".js", ".jsx"])
|
||||
.map(({ file }) => ({
|
||||
@@ -219,7 +246,7 @@ export function createFrameworkMcpServer(
|
||||
throw new Error(`WRN-MCP-TOOL: unknown tool '${name}'.`);
|
||||
};
|
||||
return {
|
||||
tools: () => TOOLS.map((tool) => ({ ...tool })),
|
||||
tools: () => toolSet.map((tool) => ({ ...tool })),
|
||||
call,
|
||||
async handle(message) {
|
||||
if (!message || typeof message !== "object")
|
||||
@@ -237,7 +264,7 @@ export function createFrameworkMcpServer(
|
||||
},
|
||||
};
|
||||
if (request.method === "tools/list")
|
||||
return { jsonrpc: "2.0", id: request.id ?? null, result: { tools: TOOLS } };
|
||||
return { jsonrpc: "2.0", id: request.id ?? null, result: { tools: toolSet } };
|
||||
if (request.method === "tools/call") {
|
||||
try {
|
||||
const value = await call(
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
#!/usr/bin/env bun
|
||||
import { createInterface } from "node:readline";
|
||||
import { createFrameworkMcpServer } from "./index.ts";
|
||||
import { createFrameworkMcpServer, type McpServerOptions } from "./index.ts";
|
||||
|
||||
export async function runMcpStdio(root = process.cwd()): Promise<void> {
|
||||
const server = createFrameworkMcpServer(root);
|
||||
export async function runMcpStdio(
|
||||
root = process.cwd(),
|
||||
options: McpServerOptions = {},
|
||||
): Promise<void> {
|
||||
const server = createFrameworkMcpServer(root, options);
|
||||
const lines = createInterface({ input: process.stdin, terminal: false });
|
||||
for await (const line of lines) {
|
||||
if (!line.trim()) continue;
|
||||
@@ -23,5 +26,8 @@ export async function runMcpStdio(root = process.cwd()): Promise<void> {
|
||||
|
||||
if (import.meta.main) {
|
||||
const root = process.argv.find((value) => value.startsWith("--root="))?.slice(7) ?? process.cwd();
|
||||
await runMcpStdio(root);
|
||||
await runMcpStdio(root, {
|
||||
design: process.argv.includes("--design") || process.argv.includes("--design-write"),
|
||||
designWrite: process.argv.includes("--design-write"),
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user