mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
* feat(craft): add brand-agnostic craft references and refero-derived lint rules Introduce `craft/` as a third top-level content axis alongside `skills/` and `design-systems/`, holding universal (brand-agnostic) craft rules that apply on top of any DESIGN.md. Skills opt in via a new `od.craft.requires` front-matter array; the daemon resolves the slug list and injects the matching files between DESIGN.md and the skill body in the system prompt. Initial vendor (MIT, adapted from referodesign/refero_skill): typography craft, color craft, anti-ai-slop. Pilot wired on saas-landing. Extend the existing lint-artifact pass with two refero-derived rules: - P0 ai-default-indigo — solid #6366f1 / #4f46e5 / #4338ca / #8b5cf6 as accent (not just gradients) is the most-reported AI tell. - P1 all-caps-no-tracking — `text-transform: uppercase` rules without ≥0.06em letter-spacing. The craft loader silently drops missing files so a skill can forward-reference future sections (e.g. `motion`) without breaking. * fix(daemon): skip :root token blocks in ai-default-indigo lint The ai-default-indigo P0 check scanned the whole HTML for the raw hex, so brands that intentionally encode indigo as `--accent: #6366f1` in :root and consume it via var(--accent) downstream were flagged as AI-default — a false positive that forced the agent to "fix" valid output. Strip :root token-definition blocks (including attribute-selector theme variants) before scanning, mirroring the existing pattern used by the raw-hex P1 check. Hex still flagged when it appears in component rules or inline styles. * docs(craft): address PR #225 P3 review feedback - craft/README.md: explain why missing craft sections are silently dropped (forward-compatibility) instead of surfacing a warning. - craft/typography.md: ground the 0.06em ALL CAPS tracking floor in Bringhurst-derived typographic practice rather than presenting the threshold as unattributed. - craft/color.md: cover the edge case where a brand's DESIGN.md intentionally encodes indigo as --accent — `var(--accent)` uses remain unflagged because the linter only inspects hardcoded hex. - docs/skills-protocol.md: link the "missing files dropped silently" note back to craft/README.md for the canonical slug list and the rationale behind the choice. * fix(craft): address PR #225 P0 review feedback - tools/pack: copy `craft/` into the packaged resource root alongside `skills`, `design-systems`, and `frames`, so the `od.craft.requires` integration isn't a silent no-op when the daemon resolves `${OD_RESOURCE_ROOT}/craft` in packaged builds. - packages/contracts: add `craftRequires?: string[]` to `SkillSummary` (and therefore `SkillDetail`) so the field that `listSkills()` already returns and `/api/skills(/:id)` already serializes via `...rest` is part of the documented web/daemon contract instead of leaking through as an untyped property. - apps/daemon/lint-artifact: expand the indigo token-strip pass to cover selector lists containing `:root` (e.g. `:root, [data-theme="light"]`) and any rule whose body is custom-property-only (e.g. a `[data-theme="dark"] { --accent: ... }` theme variant). Real component rules with a hardcoded indigo are still preserved so the P0 finding still fires; tests cover the new selector-list and theme-variant cases. * fix(craft): address PR #225 follow-up review feedback - lint-artifact: scope the indigo token-strip to <style> blocks so the rule-shaped regex no longer captures leading `<style>` text into the selector (which broke `:root` recognition for token blocks that mix `color-scheme`/etc. with `--accent`). Run the strip on the extracted CSS instead, with a regression covering `:root { color-scheme: light; --accent: #6366f1 }`. - lint-artifact: tighten the custom-property-only exemption to global theme-scope selectors (`:root`, `html`, `body`, bare attribute selectors like `[data-theme="dark"]`). Component-local rules such as `.cta { --cta-bg: #6366f1 }` are no longer exempted, so an agent cannot launder default indigo through a local var. Regression test added. - craft/anti-ai-slop.md: stop claiming every rule below is enforced by the linter; only several are. The unenforced rules (standard Hero→Features→Pricing→FAQ→CTA flow, decorative blob/wave SVG backgrounds, perfect symmetry) are now flagged inline as "(guidance, not auto-checked)" so the contract with the lint surface stays honest. * fix(daemon): tighten lint-artifact iteration and :root token gating - all-caps-no-tracking: iterate every <style> block. The previous check called `exec` once on a non-global regex, so an artifact whose offending uppercase rule sat in a second <style> block (e.g. a reset block followed by a components block) slipped past. Switch to `matchAll` and break across both loops once a violation is found. Regression test covers a second-block uppercase rule. - ai-default-indigo: stop unconditionally exempting any selector list containing `:root`. The exemption now requires both conditions to hold: every selector in the list is global theme scope AND the body is token-shaped (CSS custom properties or the `color-scheme` keyword). So `:root { background: #6366f1 }` and `:root, .cta { --cta-bg: #6366f1 }` no longer launder a hardcoded indigo through the strip pass. Regression tests cover both bypass shapes. * fix(daemon): scope theme-attr exemption and strip CSS comments in token blocks Address PR #225 review feedback on `ai-default-indigo`: - The bare-attribute branch of `selectorListIsGlobalThemeScope` accepted any `[attr=...]` selector, so a custom-property-only rule on a component/state attribute (e.g. `[data-variant="primary"]`, `[aria-current="page"]`) was treated as a global theme block and stripped before the indigo scan — exactly the component-local indigo laundering this lint is meant to catch. Restrict the exemption to a small allowlist of known theme switches: `data-theme`, `data-color-scheme`, `data-mode`. - `stripTokenBlocksFromCss` split rule bodies on `;` and matched each fragment from the start, so a token block whose body contained a normal CSS comment such as `:root { /* brand accent */ --accent: #6366f1; }` produced a fragment beginning with the comment, failed `isTokenShapedDeclaration`, and the rule was left in scope of the indigo scan — a false P0 on a legitimate token definition. Strip CSS comments before splitting/classifying declarations. Add regression coverage: arbitrary component/state attribute selectors still trip `ai-default-indigo`; `data-color-scheme` theme variants stay exempted; `:root` token blocks with leading, trailing, and between-declaration CSS comments are recognized. * fix(daemon): strip CSS comments and recognize tokens nested in at-rules The all-caps-no-tracking scan ran against raw `<style>` content, so a commented-out rule like `/* .eyebrow { text-transform: uppercase; } */` matched `upperRe` and emitted a P1 for CSS the browser ignores. Strip CSS comments from the style body before structural matching. `stripTokenBlocksFromCss` only matched flat `selector { body }` rules, so a media-query-wrapped token block like `@media (prefers-color-scheme: dark) { :root { --accent: #6366f1 } }` had its outer `@media` rule treated as the selector/body pair and the inner `:root` token block was never stripped, producing a P0 false positive on legitimate responsive theme CSS. Tighten the body alternation to `[^{}]*` so the regex matches innermost rules and recognizes the inner `:root` block directly while preserving the outer at-rule wrapper. * fix(daemon): align ai-default-indigo list with documented cardinal sins The lint's AI_DEFAULT_INDIGO subset omitted #3730a3 and #a855f7, which craft/anti-ai-slop.md lists as P0-blocked solid accents. An artifact could hard-code one of those documented colors as a button fill and slip past the indigo scan unless it happened to be inside a gradient. Bring the lint set to the exact list documented in the craft doc, and tighten the doc's wording from "etc." to an explicit enumeration that points at AI_DEFAULT_INDIGO so the prompt contract and daemon behavior stay in sync. Add regression tests pinning each newly-included hex. * fix(daemon): tighten theme-scope selector and scan inline ALL CAPS The theme-scope exemption used to accept any attribute on `:root`, `html`, or `body` (e.g. `:root[data-variant="primary"]`), letting an agent launder default indigo through a component/state attribute and slip past the `ai-default-indigo` lint. The prefixed branches now require the attribute name to be one of GLOBAL_THEME_ATTRIBUTES, matching the bare-attribute branch. The `all-caps-no-tracking` rule only iterated `<style>` blocks, so inline declarations like `<span style="text-transform: uppercase">` produced no finding even though craft/typography.md treats the ≥0.06em tracking floor as having no exceptions. Added a second scan over `style="..."` attributes that runs the same letter-spacing check and dedupes against the existing `<style>`-block finding so the agent gets a single corrective signal per artifact. * fix(daemon): align uppercase tracking px floor with the 0.06em rule The previous absolute fallback (>=1.5px) was stricter than the craft rule it enforces. `font-size: 12px; letter-spacing: 1px` is 0.083em — above the 0.06em floor — but 1.5px would reject it and trigger an unnecessary correction loop on compliant small-label CSS. Extract `hasAdequateUppercaseTracking`: read `font-size` from the same rule body and compare px tracking against `fontSize * 0.06`; fall back to a conservative >=1px floor when font-size is inherited (covers the default 16px body where 1px ≈ 0.0625em). Apply the helper to both the <style>-block scan and the inline-style scan, and add 12–14px label tests in both branches. * fix(daemon): treat rem letter-spacing as absolute, not per-element em `rem` was previously folded into the same branch as `em` and accepted at the 0.06 threshold. But `rem` is relative to the root font-size (16px default), not the element's own font-size, so on a 48px heading `letter-spacing: 0.06rem` resolves to 0.96px — about 0.02em of the element, well below the 0.06em rule the lint enforces. Convert rem to absolute px through the 16px root assumption and reuse the same px-vs-element-font-size resolution: same-rule `font-size: <n>px` gives an exact `n * 0.06` floor; otherwise the conservative >=1px fallback applies. Add regression tests for 48px headings with 0.06rem tracking (must flag) plus the 16px-element and rem-floor matches that must keep passing, in both <style>-block and inline-style branches. * fix(daemon): resolve var() refs in uppercase tracking lint `hasAdequateUppercaseTracking` only matched literal numeric values, so a tokenized rule like `letter-spacing: var(--caps-tracking)` — exactly the pattern the craft prompt steers artifacts toward — was falsely reported as `all-caps-no-tracking`. Extract `--name: value` declarations from global theme scopes (`:root`, `html`, theme-attribute selectors) once per artifact, then expand simple `var(--name)` (and `var(--name, fallback)`) references in the inspected rule body before applying the existing 0.06em / px-floor / rem-conversion logic. References without a matching token and no fallback stay in place, preserving the conservative "missing tracking" finding. * fix(daemon): resolve rem and var() font-size in uppercase tracking lint Previously the px-vs-element-font-size resolution only matched `font-size: <n>px`. Any rem-based or tokenized display size fell through to the lenient `>= 1px` body-text fallback, so an artifact emitting `.display { font-size: 3rem; text-transform: uppercase; letter-spacing: 1px; }` (a ~48px heading with a 2.88px floor) slipped past the lint that this helper exists to enforce. Resolve `rem` font-size via the same root-font assumption already used for tracking, and treat any explicitly declared but unresolvable unit (`em`, `%`, `calc(...)`, an unresolved `var(...)`) conservatively — refuse the lenient fallback so the rule must use either an `em` letter-spacing or a verifiable px/rem font-size. `var()` font-size declarations resolve through the existing `resolveCssVars` pass before the size scan runs, so the same fix catches the tokenized-display-size pattern (`--display-size: 3rem`). * fix(daemon): parse declarations to ignore custom-prop names in uppercase tracking lint The hasAdequateUppercaseTracking and resolveFontSizePx helpers used substring regexes against the rule body, so a token-name declaration such as `--letter-spacing: 0.08em` or `--display-font-size: 48px` could satisfy the `letter-spacing` / `font-size` checks even though it has no rendered effect — letting actual ALL-CAPS-without-tracking rules slip past the P1 lint. Parse the declaration list, compare exact property names, and skip declarations whose property starts with `--`. Adds regression tests covering token-name letter-spacing (style-block + inline) and a token-name font-size masking the bail-out branch. * fix(daemon): scope indigo token exemption to --accent only Previously stripTokenBlocksFromCss removed every custom-property-only global theme block before the ai-default-indigo scan, which let a laundered indigo token like `:root { --primary: #6366f1 }` consumed via `var(--primary)` slip past the lint. The craft contract is that the only escape hatch is encoding indigo as the design system's `--accent` token; any other token name is still the LLM-default color hidden behind an arbitrary name. Narrow the strip pass so a non-`--accent` token whose value carries an AI-default indigo hex keeps the rule in scope, and add regression tests for `--primary` / `--button-bg` global tokens feeding a CTA, including the at-rule and theme-attribute variants. * fix(daemon): model CSS cascade in tracking lint and detect blue→cyan trust gradients Address PR #225 review feedback (3 comments): - `letter-spacing` / `font-size` selection now picks the LAST matching declaration in the rule body, modeling CSS source-order cascade. `.eyebrow { letter-spacing: 0.08em; letter-spacing: 0.02em }` renders the noncompliant 0.02em the browser actually shows; the previous first-match behaviour silently passed it. - `extractCssTokens` now records every distinct value seen for a token across global theme scopes, and `hasAdequateUppercaseTracking` enumerates each combination so a default-theme value below the floor cannot be rescued by a scoped override that happened to be parsed later (`:root { --caps-tracking: 0.02em }` + `[data-theme="dark"] { --caps-tracking: 0.08em }` now fires). - New `trust-gradient` P0 rule pairs blue/sky tokens against cyan tokens in `linear-gradient(...)` bodies so `blue→cyan` two-stop trust gradients (documented as a cardinal sin in `craft/anti-ai-slop.md`) are actually enforced — both the hex form (`linear-gradient(90deg, #3b82f6, #06b6d4)`) and the keyword form (`linear-gradient(90deg, blue, cyan)`). Adds 11 regression tests covering each path (cascade override in <style> and inline form, font-size cascade shifting the floor, both orderings of the conflicting-token cascade, the don't-over-fire case when every theme value clears the floor, hex / keyword / sky variants of the trust gradient, and the don't-double-fire case when purple-gradient already caught a mixed gradient). * fix(daemon): apply per-scope cascade in extractCssTokens When the same CSS custom property is declared more than once inside a single rule body (e.g. `:root { --caps-tracking: 0.02em; --caps-tracking: 0.08em }`), CSS source-order cascade collapses to the last value; the earlier declaration never reaches any element. `extractCssTokens` was treating intra-scope duplicates as simultaneous theme alternatives, so `hasAdequateUppercaseTracking` enumerated the stale 0.02em and emitted a spurious all-caps-no-tracking finding. Collapse duplicate token declarations within a rule body to the last value before merging into the cross-scope distinct-value map. Cross-scope overrides (separate `:root` and `[data-theme]` rules) remain preserved as distinct values so the conservative theme-cascade check still fires when ANY applicable theme renders below the floor. * fix(daemon): scope tracking lint to innermost rules and per-theme tokens Restrict the upperRe body alternation to [^{}]* so the regex matches innermost CSS rules and skips at-rule wrappers — an outer @media or @supports could otherwise capture as a single rule whose selector was the at-rule and whose body began with the inner selector token, masking the same-rule font-size and letting noncompliant tracking on large headings slip through the lenient inherited-size fallback. Replace the by-name-distinct-values token map with per-scope token records and a buildResolvedThemes pass that materializes one effective map per theme. Paired token declarations now stay paired during evaluation, so theme variants like :root + [data-theme=dark] no longer generate cross-theme cartesian pairings (e.g. default-size + dark-track) that emit false positives on legitimate light/dark themes. --------- Co-authored-by: looper <looper@open-claude.dev>
355 lines
14 KiB
Markdown
355 lines
14 KiB
Markdown
# Skills Protocol
|
||
|
||
**Parent:** [`spec.md`](spec.md) · **Siblings:** [`architecture.md`](architecture.md) · [`agent-adapters.md`](agent-adapters.md) · [`modes.md`](modes.md)
|
||
|
||
A **Skill** is the atomic unit of design capability in OD. We adopt Claude Code's `SKILL.md` convention verbatim as the base format, then add optional fields for design-specific features (preview type, input schema, slider parameters). A skill written for plain Claude Code runs in OD. An OD skill that doesn't use our extensions runs in plain Claude Code.
|
||
|
||
> **Compatibility promise:** A skill like [`guizang-ppt-skill`](https://github.com/op7418/guizang-ppt-skill) works in OD **without modification**. It just drops into `~/.claude/skills/` and OD discovers it.
|
||
|
||
---
|
||
|
||
## 1. Base format (unchanged from Claude Code)
|
||
|
||
Every skill is a directory containing at minimum a `SKILL.md`:
|
||
|
||
```
|
||
<skill-root>/
|
||
├── SKILL.md # manifest + workflow instructions
|
||
├── assets/ # templates, images, boilerplate the skill writes
|
||
│ └── …
|
||
└── references/ # knowledge files the skill reads during planning
|
||
├── components.md
|
||
├── layouts.md
|
||
└── …
|
||
```
|
||
|
||
`SKILL.md` front-matter (YAML):
|
||
|
||
```yaml
|
||
---
|
||
name: magazine-web-ppt
|
||
description: |
|
||
Magazine-style horizontal-swipe web deck.
|
||
Trigger keywords: 杂志风 PPT, magazine deck, swipe slides.
|
||
triggers:
|
||
- "magazine deck"
|
||
- "杂志风 PPT"
|
||
- "horizontal swipe presentation"
|
||
---
|
||
```
|
||
|
||
Body is free-form Markdown that describes the workflow the agent should follow — typically a numbered step list plus principles. This is what [guizang-ppt-skill](https://github.com/op7418/guizang-ppt-skill) does.
|
||
|
||
**OD reads all of this as-is.** No changes required.
|
||
|
||
## 2. OD extensions (optional)
|
||
|
||
Skills can declare additional front-matter fields to unlock OD-specific UI. All fields are optional; absent fields fall back to sensible defaults.
|
||
|
||
```yaml
|
||
---
|
||
name: magazine-web-ppt
|
||
description: …
|
||
triggers: […]
|
||
|
||
# --- OD extensions below this line ---
|
||
|
||
od:
|
||
mode: deck # one of: prototype | deck | template | design-system
|
||
preview:
|
||
type: html # html | jsx | pptx | markdown
|
||
entry: index.html # relative path produced by the skill
|
||
reload: debounce-100 # how the preview refreshes
|
||
design_system:
|
||
requires: true # this skill reads the active DESIGN.md
|
||
sections: [color, typography] # which sections it actually uses (for prompt pruning)
|
||
craft: # universal, brand-agnostic craft references
|
||
requires: [typography, color, anti-ai-slop]
|
||
inputs: # typed inputs the user can fill in the UI
|
||
- name: title
|
||
type: string
|
||
required: true
|
||
- name: slide_count
|
||
type: integer
|
||
default: 8
|
||
min: 4
|
||
max: 20
|
||
- name: theme
|
||
type: enum
|
||
values: [editorial, minimal, brutalist, dark-glass, warm]
|
||
default: editorial
|
||
parameters: # live-tweakable sliders after first generation
|
||
- name: accent_hue
|
||
type: hue # hue | spacing | font-scale | opacity
|
||
default: 18
|
||
range: [0, 360]
|
||
- name: section_spacing
|
||
type: spacing
|
||
default: 48
|
||
range: [16, 128]
|
||
outputs:
|
||
primary: index.html
|
||
secondary: [slides.json] # for PPTX export
|
||
capabilities_required:
|
||
- surgical_edit # comment mode needs this
|
||
- file_write
|
||
---
|
||
```
|
||
|
||
### 2.1 What OD uses each field for
|
||
|
||
| Field | Used by |
|
||
|---|---|
|
||
| `od.mode` | routing (which mode picker the skill shows up under) |
|
||
| `od.preview.type` | picking the right iframe renderer |
|
||
| `od.design_system.requires` | whether to inject `DESIGN.md` |
|
||
| `od.design_system.sections` | pruning the injected DESIGN.md to relevant sections only (token savings) |
|
||
| `od.craft.requires` | which brand-agnostic `craft/<slug>.md` references to inject (e.g. `typography`, `color`, `anti-ai-slop`); injected between DESIGN.md and the skill body |
|
||
| `od.inputs` | rendering a typed form in the sidebar instead of only free-text |
|
||
| `od.parameters` | rendering live sliders that re-prompt on change |
|
||
| `od.outputs.primary` | which file the iframe loads |
|
||
| `od.outputs.secondary` | which files export pipelines read (e.g. `slides.json` for PPTX) |
|
||
| `od.capabilities_required` | gating: if the active agent lacks surgical edit, comment mode is disabled for this skill |
|
||
|
||
### 2.2 If a skill omits `od:` entirely
|
||
|
||
Defaults:
|
||
- `mode`: inferred from name/description (best-effort keyword match) or "prototype"
|
||
- `preview.type`: sniff for `*.html` → html, `*.jsx` → jsx, else "markdown"
|
||
- `preview.entry`: first file matching the sniffed type
|
||
- `design_system.requires`: true if the skill body mentions "design system" or "DESIGN.md"
|
||
- `inputs`, `parameters`: none (free-text prompt only)
|
||
|
||
The goal: **zero-config compatibility** for existing Claude Code skills.
|
||
|
||
## 3. Skill discovery & precedence
|
||
|
||
The daemon's skill registry scans three locations:
|
||
|
||
| Location | Priority | Purpose |
|
||
|---|---|---|
|
||
| `./.claude/skills/` | 1 (highest) | project-private skills, not committed |
|
||
| `./skills/` | 2 | project-committed skills |
|
||
| `~/.claude/skills/` | 3 | user-global skills |
|
||
|
||
Conflicts by `name` resolve to the higher-priority version. All locations are watched with `chokidar` in dev and re-scanned on `SIGHUP` in production.
|
||
|
||
### Symlink strategy (borrowed from [cc-switch](https://github.com/farion1231/cc-switch))
|
||
|
||
`cc-switch` maintains a central skill dir at `~/.cc-switch/skills/` and symlinks it into each agent's expected location (`~/.claude/skills/`, `~/.codex/skills/`, etc.). OD can opt into the same model:
|
||
|
||
```
|
||
~/.open-design/skills/
|
||
magazine-web-ppt/ (canonical location)
|
||
~/.claude/skills/
|
||
magazine-web-ppt → ~/.open-design/skills/magazine-web-ppt
|
||
~/.codex/skills/
|
||
magazine-web-ppt → ~/.open-design/skills/magazine-web-ppt
|
||
```
|
||
|
||
One install → every agent sees the skill. This is optional; users who only use one agent don't need it.
|
||
|
||
## 4. Skill types (by mode)
|
||
|
||
Each mode expects a slightly different skill shape. The required outputs and expected workflow differ.
|
||
|
||
### 4.1 `prototype-skill`
|
||
|
||
- **Purpose:** single-screen interactive prototype.
|
||
- **Preview:** `html` or `jsx`.
|
||
- **Primary output:** `index.html` or `Prototype.jsx`.
|
||
- **Typical workflow:** clarify brief → resolve design tokens → write component tree → write file.
|
||
- **Example skills:** `saas-landing`, `dashboard`, `login-flow`, `empty-states`.
|
||
|
||
### 4.2 `deck-skill`
|
||
|
||
- **Purpose:** multi-slide presentation.
|
||
- **Preview:** `html` (single-file deck with in-page navigation).
|
||
- **Primary output:** `index.html`.
|
||
- **Secondary output:** `slides.json` (for PPTX export).
|
||
- **Typical workflow:** clarify topic + slide count → pick theme → populate slides from layout catalog → self-check against quality rubric.
|
||
- **Reference implementation:** [guizang-ppt-skill](https://github.com/op7418/guizang-ppt-skill) — fork this for v1.
|
||
|
||
### 4.3 `template-skill`
|
||
|
||
- **Purpose:** start from a pre-built artifact; agent only personalizes content, doesn't design from scratch.
|
||
- **Preview:** inherits from the template bundle (`html` typically).
|
||
- **Primary output:** a populated copy of the template.
|
||
- **Typical workflow:** copy `assets/template/` to artifact dir → replace content placeholders → optionally tweak tokens to match design system.
|
||
- **Why separate from `prototype-skill`:** much faster (no design decisions), higher-quality floor, worse ceiling.
|
||
|
||
### 4.4 `design-system-skill`
|
||
|
||
- **Purpose:** produce a `DESIGN.md` from inputs (brand brief, screenshot, URL).
|
||
- **Preview:** `markdown` (render the resulting DESIGN.md with a sample-components preview).
|
||
- **Primary output:** `DESIGN.md`.
|
||
- **Typical workflow:** analyze input → draft 9 sections per awesome-claude-design schema → generate sample component preview → finalize.
|
||
- **Post-run:** OD prompts the user to set this DESIGN.md as the project's active design system.
|
||
|
||
## 5. The DESIGN.md as skill context
|
||
|
||
Every non–design-system skill (modes 1–3) can consume the active `DESIGN.md`. OD injects it as:
|
||
|
||
1. **System-prompt prefix** (required sections only, per `od.design_system.sections`).
|
||
2. **File available in CWD** named `DESIGN.md` — skills can `Read` it directly via their agent.
|
||
3. **Template variable** `{{ design_system }}` if the skill body references it in Mustache-style.
|
||
|
||
The 9-section DESIGN.md format is **not invented by OD**; it's the [awesome-claude-design](https://github.com/VoltAgent/awesome-claude-design) convention, reproduced here for convenience:
|
||
|
||
```markdown
|
||
# <Brand Name>
|
||
|
||
## Visual Theme & Atmosphere
|
||
## Color Palette & Roles
|
||
## Typography Rules
|
||
## Component Stylings
|
||
## Layout Principles
|
||
## Depth & Elevation
|
||
## Do's and Don'ts
|
||
## Responsive Behavior
|
||
## Agent Prompt Guide
|
||
```
|
||
|
||
Full schema and examples: [`schemas/design-system.md`](schemas/design-system.md) and [`examples/DESIGN.sample.md`](examples/DESIGN.sample.md) (TODO).
|
||
|
||
## 5.5 Craft references (`craft/`)
|
||
|
||
Some craft knowledge is **universal** — true regardless of brand. ALL CAPS always needs ≥0.06em letter-spacing; `var(--accent)` should appear at most 2 times per screen; `#6366f1` is always the AI-default tell. These rules don't belong in any one `DESIGN.md` because they apply across every brand.
|
||
|
||
OD ships these as a third axis at `<projectRoot>/craft/`:
|
||
|
||
```
|
||
craft/
|
||
├── README.md
|
||
├── typography.md
|
||
├── color.md
|
||
└── anti-ai-slop.md
|
||
```
|
||
|
||
A skill opts in by listing the slugs it needs:
|
||
|
||
```yaml
|
||
od:
|
||
craft:
|
||
requires: [typography, color, anti-ai-slop]
|
||
```
|
||
|
||
Resolution at compose time:
|
||
|
||
1. `apps/daemon/src/skills.ts` reads `od.craft.requires` from front-matter and surfaces it on the skill record.
|
||
2. `apps/daemon/src/craft.ts` reads each `<slug>.md` from `CRAFT_DIR`. Missing files are dropped silently — a skill can forward-reference `craft/motion.md` before we ship it. See [`craft/README.md`](../craft/README.md) for the canonical slug list and the rationale behind the silent-fallback choice.
|
||
3. `apps/daemon/src/prompts/system.ts` injects the concatenated craft body **between** the active DESIGN.md and the skill body. Brand tokens in DESIGN.md win on conflict; craft rules cover everything DESIGN.md does not override.
|
||
|
||
The split keeps DESIGN.md authors free of universal-craft duplication and keeps craft authors free of brand-specific drift.
|
||
|
||
## 6. Skill installation
|
||
|
||
```sh
|
||
od skill add https://github.com/op7418/guizang-ppt-skill
|
||
# → clones into ~/.open-design/skills/magazine-web-ppt
|
||
# → symlinks into ~/.claude/skills/ (and any other active agent dirs)
|
||
# → re-indexes registry
|
||
|
||
od skill add ./path/to/my-skill
|
||
# → symlinks local dir (no copy) into skills registry
|
||
|
||
od skill list
|
||
# → table: name, mode, source, agent compatibility
|
||
|
||
od skill remove <name>
|
||
# → unlinks; does not delete the source
|
||
```
|
||
|
||
## 7. Worked example — running `guizang-ppt-skill` under OD
|
||
|
||
The skill is unchanged. Here's the full path:
|
||
|
||
1. User: `od skill add https://github.com/op7418/guizang-ppt-skill`
|
||
2. Registry indexes it. No `od:` block in front-matter → defaults applied:
|
||
- `mode`: inferred from body mentioning "PPT" → `deck`.
|
||
- `preview.type`: sniffed from `assets/template.html` → `html`.
|
||
- `preview.entry`: `index.html` (convention).
|
||
- `design_system.requires`: false (skill body doesn't mention DESIGN.md).
|
||
3. User switches to `deck` mode in the web UI; skill appears in the skill picker.
|
||
4. User types "给我做一份杂志风 8 页投资人 PPT".
|
||
5. Daemon dispatches to active agent (Claude Code) with:
|
||
- system message: skill's `SKILL.md` body
|
||
- cwd: `./.od/artifacts/2026-04-24-pitch-deck/`
|
||
- files already placed in cwd: `template.html` (from skill's `assets/`)
|
||
6. Agent runs its 6-step workflow (clarify → copy template → populate → self-check → preview → refine).
|
||
7. OD streams the agent's tool calls as UI events; artifact dir grows.
|
||
8. Agent signals done; daemon sets preview iframe to `index.html`.
|
||
9. User clicks "Export PPTX" — export pipeline notices the skill has no `slides.json` output (the upstream skill doesn't produce one). OD falls back to "print to PDF then page-to-slide PPTX," which is uglier but works. This is a known limitation documented per-skill.
|
||
|
||
## 8. Writing a new skill — minimal example
|
||
|
||
```
|
||
saas-landing-skill/
|
||
├── SKILL.md
|
||
└── assets/
|
||
└── base.html
|
||
```
|
||
|
||
```markdown
|
||
---
|
||
name: saas-landing
|
||
description: |
|
||
Produce a single-page SaaS landing with hero, features, social proof, pricing, CTA.
|
||
Trigger: "saas landing", "marketing page", "product landing".
|
||
triggers:
|
||
- "saas landing"
|
||
- "marketing page"
|
||
od:
|
||
mode: prototype
|
||
preview:
|
||
type: html
|
||
entry: index.html
|
||
design_system:
|
||
requires: true
|
||
sections: [color, typography, layout, components]
|
||
inputs:
|
||
- name: product_name
|
||
type: string
|
||
required: true
|
||
- name: tagline
|
||
type: string
|
||
required: true
|
||
- name: has_pricing
|
||
type: boolean
|
||
default: true
|
||
parameters:
|
||
- name: hero_density
|
||
type: spacing
|
||
default: 96
|
||
range: [48, 200]
|
||
---
|
||
|
||
# Workflow
|
||
|
||
1. Read DESIGN.md from cwd. Adopt its color/typography/layout rules.
|
||
2. Copy `assets/base.html` to `index.html` in cwd.
|
||
3. Fill sections: hero, features (3–6), social proof, pricing (if `has_pricing`), CTA, footer.
|
||
4. Inline all CSS. Use system font stack as fallback if DESIGN.md typography fails to load.
|
||
5. Respect `hero_density` parameter as the hero section's vertical padding in px.
|
||
6. Write `index.html`. Done.
|
||
```
|
||
|
||
## 9. Testing skills
|
||
|
||
A skill ships with optional test inputs that OD uses for CI:
|
||
|
||
```
|
||
<skill-root>/
|
||
└── tests/
|
||
├── basic.prompt
|
||
├── basic.expected.manifest.json # assertions: files produced, preview.type, etc.
|
||
└── basic.expected.regex.txt # text regex assertions against the primary output
|
||
```
|
||
|
||
`od skill test <name>` runs the skill against each case using a cheap model (e.g. Haiku 4.5) and asserts on the manifest + regex. Low-fidelity but catches structural regressions.
|
||
|
||
## 10. Open questions
|
||
|
||
- **Skill signing.** Can we verify a skill hasn't been tampered with between publish and install? Simplest answer: `od skill add` records the git commit SHA; reinstall-on-update warns on signature change. Deferred to v1.
|
||
- **Skill composition.** Can a `prototype-skill` call a `deck-skill` for a sub-artifact? Not in v1; skills are leaf-level. Composition would require a meta-skill concept, which is speculative.
|
||
- **Parameter stability.** When sliders change, should the agent re-plan or just re-render? Lean: re-render (fast path), with an "also re-plan" button for larger changes.
|