@drgenius/ds-engine
v0.3.0
Published
Deterministic local MCP server + CLI for the dr-genius design system. Exposes select_components, decide_component_reuse, decide_filters, decide_filter_system over stdio. Ships a `create-app` command that scaffolds a runnable Dr Genius starter app.
Maintainers
Readme
@drgenius/ds-engine
Deterministic local MCP server + CLI + scaffold for the dr-genius design system.
Exposes four MCP tools over stdio:
| Tool | Purpose |
|---|---|
| select_components | Closed-vocabulary token → component candidates |
| decide_component_reuse | use → compose → extend → create cascade |
| decide_filters | Single-axis filter pattern selection |
| decide_filter_system | Multi-axis filter system composition |
No network. No LLM. The closed vocabulary lives in the package's compiled registry — the engine is fully self-contained at runtime.
Install
npm install --save-dev @drgenius/ds-engine
# or pnpm add -D, yarn add --devQuick start
In your consumer project (the one that will use the design system):
npx @drgenius/ds-engine initThis writes .mcp.json in your project root, telling Claude Code to launch the ds-engine MCP server.
Then:
- Open the project in Claude Code
- Approve the
ds-engineMCP server when prompted - Restart the Claude Code session (MCP servers only load at session start)
- Verify with
npx @drgenius/ds-engine health-check
CLI
ds-engine mcp start stdio MCP server
ds-engine call <tool> [--input <path>] one-shot tool invocation (input via flag XOR stdin)
ds-engine list-tools emit full tool descriptors as JSON
ds-engine health-check verify build, MCP bootability, tool registration
ds-engine init [--target <dir>] [--force]
scaffold .mcp.json in a consumer project
ds-engine create-app <name> [--target <dir>]
greenfield-only — scaffold a NEW Vite + React + Tailwind
starter app. Does not write into existing apps.
ds-engine install-blueprint <id> --target <dir> [--dry-run] [--force]
install a generated page subtree into an existing
Vite/React app. --target is REQUIRED.
ds-engine resolve-context "<brief>" [--json]
deterministic workflow / intent / feature router
for the AI rule system
ds-engine --version
ds-engine --helpHealth check
npx @drgenius/ds-engine health-check validates:
- Build artifact present (
dist/cli/index.js) - CLI reachable
- MCP server boots and responds to a JSON-RPC
tools/listwithin 3s - 4/4 expected tools registered
Exit 0 on Status: READY, 1 otherwise. Every failure prints a deterministic next step.
Scaffolding a starter app
ds-engine create-app <name> is greenfield-only: it copies a self-contained Vite 6 + React 18 + TypeScript + Tailwind v4 template into <target>/<name>, with Card, Button, HeroMetric, and KpiCard already wired and a DashboardStarter screen demonstrating canonical usage. The template ships a frozen UI snapshot (no workspace:* / file: / link: deps) and a docs/rules.md covering token usage, typography, and copy rules. Refuses non-empty targets. Does NOT run npm install automatically — prints the next steps (cd / npm install / npm run dev).
To install a generated page into an existing app, use install-blueprint instead.
Installing a blueprint into an existing app
ds-engine install-blueprint <id> --target <dir> [--dry-run] [--force] writes a self-contained page subtree under <target>/src/pages/<slug>/ from a bundled blueprint. The CLI is strictly distinct from create-app and never silently writes to a temp app or to cwd — --target is required and must point at an existing Vite/React project (validated against package.json + src/ + a Vite/React signal).
Bundled blueprints (v3.0):
| Id | Archetype | Files emitted |
|---|---|---|
| agent-performance | reporting | page entry + 3 sections + 1 mocks file under src/pages/agent-performance/ |
Preconditions:
- The target must be a Vite/React app (validated against
package.json+src/+ a Vite/React signal). - Bundled blueprints emit code that uses Tailwind utility classes and DS semantic class names (
type-*,text-fg-*,bg-surface-*,border-border-*). The CLI emits a non-blocking warning if no Tailwind signal is detected (notailwindcss/@tailwindcss/vite/@tailwindcss/postcssdep, notailwind.config.*) — the install proceeds, but the page may render unstyled. The CLI does NOT install dependencies; you opt in to Tailwind in your own toolchain. - The bundled
agent-performanceblueprint also importsCard,CardHeader,CardBody,Button, andKpiCardfrom<target>/src/components/ui/*— the same paths thecreate-appstarter ships. If you install into a project that does not ship those primitives at those paths, the emitted page will not compile until you provide them.
Behavior:
- Write scope is hard-restricted to
src/pages/<slug>/**. The manifest validator rejects anytopath outside that subtree. - Default mode is atomic: any pre-existing-different file in the plan aborts the whole install with exit 1 and writes nothing. Use
--dry-runto inspect. --dry-runemits the full plan as JSON on stdout ({ command, blueprint, target, tokens, plan, postInstall }), exits 0, writes nothing.--forceoverwrites pre-existing-different files declared by the manifest only. It never touches anything else.- The CLI never auto-modifies your router or nav config. The
postInstall.routeandpostInstall.navEntryblocks are reported on stderr after a successful install — you wire them in manually.
Examples:
ds-engine install-blueprint agent-performance --target ./app
ds-engine install-blueprint agent-performance --target ./app --dry-run
ds-engine install-blueprint agent-performance --target ./app --forceBlueprint authoring contract (manifest-driven):
{
"id": "agent-performance",
"version": "1.0.0",
"engine": ">=0.3.0",
"archetype": "reporting", // must match a registered blueprint id
"tokens": {
"slug": { "default": "agent-performance", "pattern": "^[a-z][a-z0-9-]*$" },
"Slug": { "derive": "pascalCase(slug)" }, // pascalCase | camelCase | kebabCase | titleCase
"title": { "default": "Agent performance" }
},
"files": [
{ "from": "files/index.tsx", "to": "src/pages/{{slug}}/index.tsx" }
],
"postInstall": {
"route": { "path": "/{{slug}}", "componentImport": "./pages/{{slug}}", "componentName": "{{Slug}}" },
"navEntry": { "label": "{{title}}", "path": "/{{slug}}", "section": "reporting" }
}
}The manifest is the complete install contract — every effect of an install is derivable from it. Tokens use string substitution only (no conditionals in v3.0). Files outside src/pages/{{slug}}/ are forbidden by the validator.
Engine API (programmatic)
The package exports a single engine object plus typed namespaces:
import { engine, resolveContext, resolveRegistry } from "@drgenius/ds-engine";
engine.checks.runAll(...) // run all design-system guardrail checks
engine.mcp.availableTools // descriptors for the 4 tools
engine.selection.selectComponents() // direct call to the selection algorithm
engine.reuse.decideReuse() // direct call to the reuse cascade
engine.context.map // ContextMap (workflows, screen intents, features)
engine.context.resolve(...) // workflow + intent + feature resolver
engine.registry.components() // frozen ComponentEntry[]
engine.registry.patterns() // frozen PatternEntry[]
engine.registry.vocabulary() // closed-vocabulary set
engine.registry.blueprints // 5 page blueprints (dashboard, reporting, settings, ftu, master-detail)
engine.registry.sectionArchetypes // section-archetype metadata
engine.registry.getBlueprint(id)
engine.registry.getSectionArchetype(id)
engine.registry.getBlueprintSections(id)
engine.registry.validateBlueprintRegistry()Registry resolver
resolveRegistry({ source }) returns { root, source, version } for the current registry data location. Three modes:
| source | Behavior |
|---|---|
| "bundled" (default) | Prefer the post-scrub dist-registry/ shipped in the npm tarball. |
| "path" | Caller-supplied absolute path; useful for tests and overrides. |
| "package" | Resolve the workspace-only @drgenius/registry-core package (dev/test). |
getResolvedRegistryPaths() returns the per-file paths (blueprints, sectionArchetypes) actually used by the loader. Both helpers are stable and let consumers introspect the active registry without parsing JSON themselves.
Versioning
@drgenius/ds-engine and the bundled registry data carry independent semver versions. A given engine release ships a registry snapshot whose version may differ from the engine's own. For example, @drgenius/[email protected] ships registry data at version 0.1.0. This is intentional: registry content evolves independently of CLI/MCP behavior.
| Version | Source |
|---|---|
| Engine | package.json#version (matches the npm tarball name) |
| Bundled registry | dist-registry/manifest.json#registryVersion, written at prepack from the workspace @drgenius/registry-core package |
| Runtime read | resolveRegistry().version is the authoritative runtime source of truth |
Changes to bundled component, pattern, blueprint, or section-archetype data bump the registry version independently of engine releases. Changes to the engine runtime (CLI, MCP tools, resolver, checks) bump the engine version independently of the registry.
Troubleshooting
| Symptom | Fix |
|---|---|
| /genius-ui not found in Claude Code | Restart the Claude Code session. |
| Tools not available / MCP not active | Run npx @drgenius/ds-engine health-check. Follow the printed hint. |
| Build missing from health-check | Reinstall: npm install @drgenius/ds-engine (the postinstall in your project should rebuild; if running from source, npm run build inside the package). |
| UNKNOWN_VOCABULARY from a tool call | Token isn't in the closed vocabulary — Claude rephrases automatically. |
| INVALID_CONSUMER_ROOT_PATH from decide_component_reuse | The path you passed resolves inside the installed @drgenius/ds-engine package. Use a path in your own consumer project. |
Path safety
decide_component_reuse produces local component proposals at ${consumerRootPath}/.drgenius/components/<Name>.tsx. The engine refuses any consumerRootPath that resolves inside its own installed package directory — paths are compared by resolved location, not by string match. A consumer project named dr-genius-ds-customer is accepted.
Determinism
Identical inputs produce byte-identical outputs across runs. The cascade order in decide_component_reuse (use → compose → extend → create) is hard-wired; there is no fuzzy matching, no embedding lookup, no LLM call.
License
Private — internal use only.
