Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when you derive, change or extend a design system - where tokens live per stack, the DTCG token tree, role-based names for both themes, base vs layer vs drift, the DESIGN.md section order, the inventory format, the contrast check and the lint every proposal returns
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
A design system the team will actually build with is derived from the code that already exists, named by role, proven against contrast in both themes, and proposed — never invented in a vacuum. The proposal is what every developer run in those repositories will build with from the day it is approved, so a vague token or a missing dark value becomes a hundred one-off decisions downstream.
Core principle: Read everything first, keep what is shared, give every deliberate difference a reason, and report the rest as drift.
Read every repository with a UI (clone the ones not in your workspace: git clone --depth 1 <root_path or remote_url> _design/<name>, then grep_code/read_file with path: "_design/<name>"). Note every value with the file and line it came from.
| Stack | Tokens live in | Fonts | Components |
|---|---|---|---|
| Web, Tailwind v4 | @theme in src/index.css / app/globals.css; :root and .dark / [data-theme=dark] CSS variables | <link> in index.html, @font-face, next/font in app/layout.tsx | src/components/{ui,atoms,molecules,…}, INVENTORY.md, components.json, *.stories.tsx |
| Web, Tailwind v3 | tailwind.config.{js,ts} theme.extend + the CSS variables it points at | same | same |
| Web, CSS-in-JS | createTheme (MUI), extendTheme (Chakra), theme.ts (styled-components / emotion) | same | same |
| iOS (SwiftUI) | Assets.xcassets/**/*.colorset/Contents.json (Any/Dark), Theme.swift, Color+*.swift, Font+*.swift | UIAppFonts in Info.plist | Views/Components/, DesignSystem/ |
| Android (Compose) | ui/theme/Color.kt, Theme.kt, Type.kt, Shape.kt | res/font/ | ui/components/ |
| Android (views) | res/values/colors.xml, themes.xml, values-night/, dimens.xml | res/font/ | res/layout/, custom views |
| Flutter | ThemeData, ColorScheme, ThemeExtensions (lib/ui/core/theme/) | fonts: in pubspec.yaml | lib/ui/core/{atoms,molecules,organisms} |
Find the real palette by frequency, not by the theme file alone — a value used in 40 places is the palette, one used once is a one-off or drift. Read-only, in the workspace:
grep -rhoE '#[0-9a-fA-F]{6}\b' src | tr 'a-f' 'A-F' | sort | uniq -c | sort -rn | head -40
grep -rhoE 'Color\(0x[0-9A-Fa-f]{8}\)' lib | sort | uniq -c | sort -rn | head -40
A new product with no UI code yet: the brief is the source. Ground every choice in the audience, the subject matter and the references the brief names (rule avoid-ai-default-looks applies hardest here), make ONE deliberate proposal rather than a menu, and write each decision with its reason in DESIGN.md's Overview — the human approves or annotates it like any other.
background, foreground, surface, surface-raised, muted, muted-foreground, border, input, ring, and each action/status colour with its text-on-it pair: primary/on-primary, secondary/on-secondary, accent/on-accent, destructive/on-destructive, success, warning, info (each with on-*). Keep the convention the code already uses (primary-foreground in a shadcn repo) — one convention per design system.font.family.sans / font.family.display / font.family.mono, a font.size scale, font.weight, line-height, and the named roles (typography.heading-1, body, body-sm, label, caption) as composite tokens.space.1 = 4px … space.16 = 64px); radius (radius.sm/md/lg/full); shadow by elevation (shadow.sm/md/lg); motion (duration.fast/normal, easing.standard); layout (breakpoint.sm/md/lg/xl, container.max); on mobile size.touch-target.blue-500 is a palette value, not a role. A raw palette may exist as its own palette group that role tokens alias ("$value": "{palette.green.700}"); components only ever see roles.Groups are objects; a token is an object with $value (and $type, on the token or inherited from its group; $description where the use is not obvious). Values in the form the code writes them — #RRGGBB hex by default (the contrast check reads it), 16px, 150ms. Both themes carry the SAME keys; a key missing from one theme is a defect.
{
"color": {
"$type": "color",
"light": {
"background": { "$value": "#FFFFFF" },
"foreground": { "$value": "#1C2024" },
"muted-foreground": { "$value": "#5A6560" },
"primary": { "$value": "#1F5F4A", "$description": "Primary action, links, focus ring" },
"on-primary": { "$value": "#FFFFFF" }
},
"dark": {
"background": { "$value": "#111416" },
"foreground": { "$value": "#E8ECEA" },
"muted-foreground": { "$value": "#9AA5A0" },
"primary": { "$value": "#5FB894" },
"on-primary": { "$value": "#0B1F17" }
}
},
"font": {
"family": { "sans": { "$type": "fontFamily", "$value": ["Source Sans 3", "system-ui", "sans-serif"] } },
"size": { "$type": "dimension", "body": { "$value": "16px" }, "heading-1": { "$value": "32px" } }
},
"space": { "$type": "dimension", "1": { "$value": "4px" }, "2": { "$value": "8px" }, "4": { "$value": "16px" } },
"radius": { "$type": "dimension", "md": { "$value": "6px" } },
"duration": { "$type": "duration", "fast": { "$value": "150ms" } }
}
A repository layer holds ONLY the tokens it adds or overrides — a layer token replaces the base token whole; a group merges key by key. Never null (refused: a layer cannot delete a base token), never a copy of the base.
| The difference | It is | It goes |
|---|---|---|
| Every UI repository does it | base | the project base |
| One repository differs on purpose, for a reason a user or a platform gives — a marketing site's campaign colours, iOS 44pt / Android 48dp touch targets, a TV app's larger type | layer | that repository's layer, with the reason in rationale |
One repository differs because a value was copied and changed — #1F5F4A vs #1F5F4B, 15px body vs 16px, a fourth grey | drift | the report's drift table; the Apply task after approval fixes it |
| The code differs from an already-approved design system | drift | same |
When unsure, it is drift — a layer needs a reason you can write in one sentence. A repository with no project base gets a layer-only, complete design system (every token, DESIGN.md, inventory). If the repository belongs to several projects and none is chosen as its base, ask with record_open_questions before proposing anything.
Proposing: base first (scope: "project", project_id, design_md, tokens, inventory_md), then each layer (scope: "repository", repository_id, tokens, rationale). Calling it again for the same target from the same task replaces that pending version — that is how you revise. The human approves every version at once by approving the task.
Every propose_design_system result carries lint, and every version get_design_system returns carries it too:
| Code | Severity | Means | Fix |
|---|---|---|---|
unresolved_alias | error | {palette.green.700} names a token that does not exist | point it at a real token, or add that token |
invalid_color | error | a colour value that is not hex, rgb(), hsl(), oklch(), oklab() or a DTCG colour object | write it as #RRGGBB, the form the code uses |
contrast_below_aa | error | a colour and its -foreground / on- pair below 4.5:1, with the ratio | change the token until the pair passes — never footnote it |
empty_group | warning | a group with no tokens in it | fill it or drop it |
missing_section | warning | a DESIGN.md section the base lacks | write the section (§5) |
Fix every error and propose again before the report is attached — a version with an error finding never goes to review. A warning you leave on purpose is named in the report with its reason.
Plain markdown, under ~8 KB. A layer's design_md covers only what the layer changes and why.
One line per component, grouped by level, with where it lives in each repository:
atom · Button · actions · primary/secondary/ghost/destructive × sm/md/lg · default/hover/focus/active/disabled/loading · web: src/components/atoms/Button.tsx · ios: Views/Components/AppButton.swift
molecule · SearchField · filter a list · with/without clear · default/focus/filled/disabled · web: src/components/molecules/SearchField.tsx · ios: — (missing)
A component one repository lacks is listed with — (missing); two implementations of the same thing in one repository are drift.
Body text ≥ 4.5:1; large text (≥ 24px, or ≥ 18.66px bold) and UI boundaries, icons and focus rings ≥ 3:1. Check every on-* against its colour, foreground and muted-foreground against background and surface, primary as link text on background, ring against background. Read-only, nothing written:
python3 - <<'PY'
def lum(h):
h = h.lstrip('#')
c = [int(h[i:i + 2], 16) / 255 for i in (0, 2, 4)]
c = [x / 12.92 if x <= 0.04045 else ((x + 0.055) / 1.055) ** 2.4 for x in c]
return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]
def ratio(a, b):
hi, lo = sorted((lum(a), lum(b)), reverse=True)
return (hi + 0.05) / (lo + 0.05)
for name, fg, bg in [
("light foreground/background", "#1C2024", "#FFFFFF"),
("light muted-foreground/background", "#5A6560", "#FFFFFF"),
("dark on-primary/primary", "#0B1F17", "#5FB894"),
]:
r = ratio(fg, bg)
print(f"{name}: {r:.2f} {'AA' if r >= 4.5 else 'large text only' if r >= 3 else 'FAIL'}")
PY
Convert OKLCH/HSL values to hex first. A failing pair is fixed in the token, never footnoted. The server's lint checks each colour against its -foreground / on- pair; this check covers the rest — muted text, links, the focus ring. The ratio table goes into the report.
ONE self-contained HTML document (sanitizer-safe-html), titled design system: <project or repository> v<N>: swatches for both themes (each with its token name, value, a text sample in its on-* colour and the ratio); the type scale set in real copy; spacing, radius and elevation samples; the core components drawn in their states; per repository a table — token · today in the repository (file:line) · design system · layer or drift; and the lint warnings left, each with its reason. It is what the human approves; everything a reviewer may question is text, not only a picture. Serve it on loopback (screen-mockup-html's self-check) and save a full-page browser_screenshot with attach_to_task: true and title: "design-system-<name>-v<N>", so the human sees the palette on the task.
web (Tailwind v4): --primary #1F5F4A (src/index.css:14), body 16px, buttons 40px tall
ios (SwiftUI): Primary.colorset #1F5F4B, body .body (17pt), buttons 44pt tall
#1F5F4A vs #1F5F4B → drift (one digit, no reason) → base color.light.primary #1F5F4A; drift row for ios
17pt vs 16px body → layer for ios: font.size.body 17px, rationale "iOS Dynamic Type default body size"
44pt vs 40px → layer for ios: size.touch-target 44px, rationale "Apple HIG minimum hit target"
green-700) where a role belongs.propose_design_system result still lists an error.rationale that describes the value instead of the reason ("uses a darker green").아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.