@ryuxdsgn/design-intelligence 2.7.1 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +49 -39
- package/dist/intelligence/content.js +5 -5
- package/dist/intelligence/guides.js +15 -6
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -133,9 +133,10 @@ async function runInstall(flags) {
|
|
|
133
133
|
return;
|
|
134
134
|
}
|
|
135
135
|
p.intro(pc.bgCyan(pc.black(" RYUX ")));
|
|
136
|
+
p.log.message(KEY_LEGEND);
|
|
136
137
|
const detected = agentsAt(scope, false);
|
|
137
138
|
const a = await p.multiselect({
|
|
138
|
-
message:
|
|
139
|
+
message: "Which agents do you use? Pick any (a selects all).",
|
|
139
140
|
options: [
|
|
140
141
|
...AGENT_TARGETS.map((x) => ({ value: x.id, label: x.label, hint: scope.global ? `~/${x.globalDir}` : x.dir })),
|
|
141
142
|
{ value: AGENTS_MD_INLINE.id, label: AGENTS_MD_INLINE.label, hint: "AGENTS.md" },
|
|
@@ -145,11 +146,8 @@ async function runInstall(flags) {
|
|
|
145
146
|
});
|
|
146
147
|
if (p.isCancel(a))
|
|
147
148
|
return cancel();
|
|
148
|
-
const m = await p.confirm({ message: `Show how to connect the ryux MCP?${keys(YES_NO, "Enter continue")}`, initialValue: false });
|
|
149
|
-
if (p.isCancel(m))
|
|
150
|
-
return cancel();
|
|
151
149
|
agents = a;
|
|
152
|
-
showMcp =
|
|
150
|
+
showMcp = false;
|
|
153
151
|
}
|
|
154
152
|
if (scope.global && agents.includes(AGENTS_MD_INLINE.id)) {
|
|
155
153
|
console.log(pc.yellow("agents-md is project-only; skipped for --global."));
|
|
@@ -170,7 +168,7 @@ async function runInstall(flags) {
|
|
|
170
168
|
if (nonInteractive)
|
|
171
169
|
return;
|
|
172
170
|
if (!scope.global) {
|
|
173
|
-
const s = await p.confirm({ message:
|
|
171
|
+
const s = await p.confirm({ message: "Set up project context now? Every question can be skipped.", initialValue: true });
|
|
174
172
|
if (p.isCancel(s))
|
|
175
173
|
return cancel();
|
|
176
174
|
if (s && !(await setupInteractive(scope.root)))
|
|
@@ -216,7 +214,7 @@ async function runRemove(flags) {
|
|
|
216
214
|
return;
|
|
217
215
|
}
|
|
218
216
|
if (!flags.yes) {
|
|
219
|
-
const ok = await p.confirm({ message: `Remove RYUX from ${scope.global ? "your home directory" : "this project"}
|
|
217
|
+
const ok = await p.confirm({ message: `Remove RYUX from ${scope.global ? "your home directory" : "this project"}?` });
|
|
220
218
|
if (p.isCancel(ok) || !ok) {
|
|
221
219
|
console.log("Cancelled.");
|
|
222
220
|
return;
|
|
@@ -313,11 +311,11 @@ function detectContext(root) {
|
|
|
313
311
|
];
|
|
314
312
|
return { name, system: candidates.filter((c) => existsSync(join(root, c))) };
|
|
315
313
|
}
|
|
316
|
-
/**
|
|
314
|
+
/** Every question also offers "Write my own" and "Skip, leave unknown"; a skipped field stays unknown. */
|
|
317
315
|
const CONTEXT_FIELDS = [
|
|
318
316
|
{
|
|
319
317
|
label: "Product", hint: "(what it does, in one sentence:)",
|
|
320
|
-
ask: { flag: "product", group: "Project", question: "What kind of product is it?", options: [
|
|
318
|
+
ask: { flag: "product", group: "Project", question: "What kind of product is it?", example: "Ticketing for local events", options: [
|
|
321
319
|
{ value: "Online store", label: "Online store" },
|
|
322
320
|
{ value: "Payments or finance", label: "Payments or finance" },
|
|
323
321
|
{ value: "Marketplace", label: "Marketplace" },
|
|
@@ -329,18 +327,17 @@ const CONTEXT_FIELDS = [
|
|
|
329
327
|
},
|
|
330
328
|
{
|
|
331
329
|
label: "Current work", hint: "",
|
|
332
|
-
ask: { flag: "work", group: "Project", question: "What are you building right now?", options: [
|
|
330
|
+
ask: { flag: "work", group: "Project", question: "What are you building right now?", example: "The checkout flow", options: [
|
|
333
331
|
{ value: "A new product", label: "A new product" },
|
|
334
332
|
{ value: "A new feature", label: "A new feature" },
|
|
335
333
|
{ value: "A new screen", label: "A new screen" },
|
|
336
334
|
{ value: "An existing flow", label: "An existing flow", hint: "changing something that already exists" },
|
|
337
335
|
{ value: "A redesign", label: "A redesign" },
|
|
338
|
-
{ value: "", label: "Not decided yet", hint: "stays unknown" },
|
|
339
336
|
] },
|
|
340
337
|
},
|
|
341
338
|
{
|
|
342
339
|
label: "Audience", hint: "",
|
|
343
|
-
ask: { flag: "audience", group: "Project", question: "Who is it for?", options: [
|
|
340
|
+
ask: { flag: "audience", group: "Project", question: "Who is it for?", example: "Freelancers who send invoices", options: [
|
|
344
341
|
{ value: "Consumers", label: "Consumers", hint: "people using it for themselves" },
|
|
345
342
|
{ value: "Small business owners", label: "Small business owners" },
|
|
346
343
|
{ value: "Professionals at work", label: "Professionals at work" },
|
|
@@ -350,11 +347,10 @@ const CONTEXT_FIELDS = [
|
|
|
350
347
|
},
|
|
351
348
|
{
|
|
352
349
|
label: "Market and locale", hint: "(for example Indonesia, id-ID, Rupiah; or global, en-US, USD)",
|
|
353
|
-
ask: { flag: "market", group: "Project", question: "Where is it used? This sets language and currency.", options: [
|
|
350
|
+
ask: { flag: "market", group: "Project", question: "Where is it used? This sets language and currency.", example: "Malaysia (ms-MY, MYR)", options: [
|
|
354
351
|
{ value: "Global, several markets", label: "Global, several markets" },
|
|
355
352
|
{ value: "Indonesia (id-ID, IDR)", label: "Indonesia", hint: "id-ID, Rupiah" },
|
|
356
353
|
{ value: "United States (en-US, USD)", label: "United States", hint: "en-US, US dollar" },
|
|
357
|
-
{ value: "", label: "Not decided yet", hint: "stays unknown" },
|
|
358
354
|
] },
|
|
359
355
|
},
|
|
360
356
|
{ label: "Brand and design system", hint: "" },
|
|
@@ -362,19 +358,20 @@ const CONTEXT_FIELDS = [
|
|
|
362
358
|
{ label: "Evidence sources", hint: "RYUX MCP (not available yet), Figma files, reference URLs:" },
|
|
363
359
|
{
|
|
364
360
|
label: "Constraints", hint: "platforms, accessibility target (for example WCAG 2.2 AA), what must not change:",
|
|
365
|
-
ask: { flag: "constraints", group: "Project", question: "
|
|
361
|
+
ask: { flag: "constraints", group: "Project", question: "Where does it run?", example: "Tablet app for shop staff", options: [
|
|
366
362
|
{ value: "Mobile app", label: "Mobile app" },
|
|
367
|
-
{ value: "
|
|
368
|
-
{ value: "
|
|
363
|
+
{ value: "Responsive website", label: "Website", hint: "phone and desktop" },
|
|
364
|
+
{ value: "Desktop website", label: "Desktop website" },
|
|
369
365
|
{ value: "Web and mobile app", label: "Web and mobile app" },
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
366
|
+
], then: { question: "What must stay as it is? Pick any.", example: "Our current logo and colours", multi: true, options: [
|
|
367
|
+
{ value: "Existing design system", label: "Existing design system" },
|
|
368
|
+
{ value: "Existing brand identity", label: "Existing brand identity" },
|
|
369
|
+
{ value: "Accessibility (WCAG 2.2 AA)", label: "Accessibility", hint: "WCAG 2.2 AA" },
|
|
370
|
+
] } },
|
|
374
371
|
},
|
|
375
372
|
{
|
|
376
373
|
label: "Design intent", hint: "(what users should understand, feel, and do)",
|
|
377
|
-
ask: { flag: "intent", group: "Design", question: "What should people get from it?", options: [
|
|
374
|
+
ask: { flag: "intent", group: "Design", question: "What should people get from it?", example: "Know when their order arrives", options: [
|
|
378
375
|
{ value: "Understand their status at a glance", label: "Understand their status at a glance" },
|
|
379
376
|
{ value: "Finish a task quickly", label: "Finish a task quickly" },
|
|
380
377
|
{ value: "Feel safe and in control", label: "Feel safe and in control" },
|
|
@@ -383,7 +380,7 @@ const CONTEXT_FIELDS = [
|
|
|
383
380
|
},
|
|
384
381
|
{
|
|
385
382
|
label: "UX direction", hint: "",
|
|
386
|
-
ask: { flag: "ux", group: "Design", question: "How should the experience help users?", options: [
|
|
383
|
+
ask: { flag: "ux", group: "Design", question: "How should the experience help users?", example: "Works one-handed at a shop counter", options: [
|
|
387
384
|
{ value: "Fast for people who use it often", label: "Fast for people who use it often" },
|
|
388
385
|
{ value: "Clear for people using it for the first time", label: "Clear for people using it for the first time" },
|
|
389
386
|
{ value: "Easy to scan and understand", label: "Easy to scan and understand" },
|
|
@@ -392,7 +389,7 @@ const CONTEXT_FIELDS = [
|
|
|
392
389
|
},
|
|
393
390
|
{
|
|
394
391
|
label: "UI direction", hint: "(character, for example calm, trustworthy, restrained)",
|
|
395
|
-
ask: { flag: "ui", group: "Design", question: "What character should it have? A character, not colors or fonts. Pick up to 3.", multi: true, max: 3, options: [
|
|
392
|
+
ask: { flag: "ui", group: "Design", question: "What character should it have? A character, not colors or fonts. Pick up to 3.", example: "Warm, precise", multi: true, max: 3, options: [
|
|
396
393
|
{ value: "Calm", label: "Calm" },
|
|
397
394
|
{ value: "Trustworthy", label: "Trustworthy" },
|
|
398
395
|
{ value: "Friendly", label: "Friendly" },
|
|
@@ -405,7 +402,7 @@ const CONTEXT_FIELDS = [
|
|
|
405
402
|
},
|
|
406
403
|
{
|
|
407
404
|
label: "Motion direction", hint: "(feel, what motion communicates, what to avoid)",
|
|
408
|
-
ask: { flag: "motion", group: "Design", question: "How much motion?", options: [
|
|
405
|
+
ask: { flag: "motion", group: "Design", question: "How much motion?", example: "Only to confirm a payment went through", options: [
|
|
409
406
|
{ value: "No motion unless necessary", label: "No motion unless necessary" },
|
|
410
407
|
{ value: "Subtle, only to show change", label: "Subtle, only to show change" },
|
|
411
408
|
{ value: "Expressive, part of the brand", label: "Expressive, part of the brand" },
|
|
@@ -472,19 +469,17 @@ async function writeContext(root, answers) {
|
|
|
472
469
|
await upsertBlock(path, body, CONTEXT_MARKS);
|
|
473
470
|
return body;
|
|
474
471
|
}
|
|
475
|
-
/**
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const YES_NO = "←/→ or y/n choose";
|
|
480
|
-
const TEXT_KEYS = "Type, then Enter save (empty skips)";
|
|
472
|
+
/** The text box hint: an example answer, and that an empty Enter skips. */
|
|
473
|
+
const typed = (example) => `e.g. ${example} · empty Enter skips`;
|
|
474
|
+
/** Shown once at the start, not under every question (it stayed on screen after each answer). */
|
|
475
|
+
const KEY_LEGEND = pc.dim("↑/↓ move · Space select · Enter continue · Ctrl+C cancel");
|
|
481
476
|
const OWN = "\u0000own";
|
|
482
477
|
const KEEP = "\u0000keep";
|
|
483
478
|
const SKIP = "\u0000skip";
|
|
484
479
|
/** One select: Keep (when set), the choices, Write my own, Skip. Returns "" for skip, null on cancel. */
|
|
485
|
-
async function askOne(question, options, current) {
|
|
480
|
+
async function askOne(question, options, current, example) {
|
|
486
481
|
const v = await p.select({
|
|
487
|
-
message:
|
|
482
|
+
message: question,
|
|
488
483
|
options: [
|
|
489
484
|
...(current ? [{ value: KEEP, label: `Keep: ${current}` }] : []),
|
|
490
485
|
...options.map((o) => ({ value: o.value || SKIP, label: o.label, hint: o.hint })),
|
|
@@ -498,19 +493,19 @@ async function askOne(question, options, current) {
|
|
|
498
493
|
if (v === KEEP || v === SKIP)
|
|
499
494
|
return "";
|
|
500
495
|
if (v === OWN) {
|
|
501
|
-
const t = await p.text({ message:
|
|
496
|
+
const t = await p.text({ message: question, placeholder: typed(example) });
|
|
502
497
|
return p.isCancel(t) ? null : (t ?? "").trim();
|
|
503
498
|
}
|
|
504
499
|
return v;
|
|
505
500
|
}
|
|
506
501
|
/** A multiselect: pick any, or none to skip. Returns "" for skip, null on cancel. */
|
|
507
|
-
async function askMany(question, options, current, max) {
|
|
502
|
+
async function askMany(question, options, current, example, max) {
|
|
508
503
|
const parts = current ? current.split(",").map((x) => x.trim()).filter(Boolean) : [];
|
|
509
504
|
const known = options.map((o) => o.value);
|
|
510
505
|
const custom = parts.filter((x) => !known.includes(x)).join(", ");
|
|
511
506
|
for (;;) {
|
|
512
507
|
const v = await p.multiselect({
|
|
513
|
-
message:
|
|
508
|
+
message: question,
|
|
514
509
|
options: [
|
|
515
510
|
...(custom ? [{ value: KEEP, label: `Keep: ${custom}` }] : []),
|
|
516
511
|
...options.map((o) => ({ value: o.value, label: o.label, hint: o.hint })),
|
|
@@ -526,7 +521,7 @@ async function askMany(question, options, current, max) {
|
|
|
526
521
|
if (picked.includes(KEEP))
|
|
527
522
|
values.push(custom);
|
|
528
523
|
if (picked.includes(OWN)) {
|
|
529
|
-
const t = await p.text({ message:
|
|
524
|
+
const t = await p.text({ message: question, placeholder: typed(example) });
|
|
530
525
|
if (p.isCancel(t))
|
|
531
526
|
return null;
|
|
532
527
|
if (t?.trim())
|
|
@@ -551,12 +546,26 @@ async function askContext(root) {
|
|
|
551
546
|
p.log.step(group === "Project" ? "Project setup" : "Design setup");
|
|
552
547
|
}
|
|
553
548
|
const current = fieldValue(body, f);
|
|
554
|
-
|
|
549
|
+
if (ask.then) {
|
|
550
|
+
// Two questions, one field: split what is stored between them, and keep a part that is skipped.
|
|
551
|
+
const parts = current.split(",").map((x) => x.trim()).filter(Boolean);
|
|
552
|
+
const first = parts.find((x) => ask.options.some((o) => o.value === x)) ?? "";
|
|
553
|
+
const rest = parts.filter((x) => x !== first).join(", ");
|
|
554
|
+
const a = await askOne(ask.question, ask.options, first, ask.example);
|
|
555
|
+
if (a === null)
|
|
556
|
+
return null;
|
|
557
|
+
const b = ask.then.multi ? await askMany(ask.then.question, ask.then.options, rest, ask.then.example) : await askOne(ask.then.question, ask.then.options, rest, ask.then.example);
|
|
558
|
+
if (b === null)
|
|
559
|
+
return null;
|
|
560
|
+
answers[f.label] = [a || first, b || rest].filter(Boolean).join(", ");
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
const v = ask.multi ? await askMany(ask.question, ask.options, current, ask.example, ask.max) : await askOne(ask.question, ask.options, current, ask.example);
|
|
555
564
|
if (v === null)
|
|
556
565
|
return null;
|
|
557
566
|
answers[f.label] = v;
|
|
558
567
|
if (f.label === "Product" && v) {
|
|
559
|
-
const t = await p.text({ message:
|
|
568
|
+
const t = await p.text({ message: "In one sentence, what does it do?", placeholder: typed("A wallet for paying at shops by QR and sending money to friends") });
|
|
560
569
|
if (p.isCancel(t))
|
|
561
570
|
return null;
|
|
562
571
|
if (t?.trim())
|
|
@@ -599,6 +608,7 @@ async function runSetup(flags) {
|
|
|
599
608
|
return;
|
|
600
609
|
}
|
|
601
610
|
p.intro(pc.bgCyan(pc.black(" RYUX setup ")));
|
|
611
|
+
p.log.message(KEY_LEGEND);
|
|
602
612
|
if (!(await setupInteractive(root)))
|
|
603
613
|
return cancel();
|
|
604
614
|
p.outro(`RYUX is ready. Next: ${CLI_CMD} check`);
|
|
@@ -710,11 +710,11 @@ export const RULES = [
|
|
|
710
710
|
title: "Tokens over one-off values",
|
|
711
711
|
level: "preferred",
|
|
712
712
|
gate: "lock",
|
|
713
|
-
do: "Use tokens or variables for color, spacing, radius, elevation, and type.",
|
|
714
|
-
dont: "Hard-code one-off values for things the system already defines.",
|
|
715
|
-
why: "Tokens keep changes consistent and reviewable.",
|
|
716
|
-
basis: "W3C Design Tokens Community Group",
|
|
717
|
-
check: "review",
|
|
713
|
+
do: "Use tokens or variables for color, spacing, radius, elevation, and type. First look for the ones that exist (DESIGN.md, a tokens or theme file in the repo, the design file's variables and components, a linked library) and use them. When the project has none, define them before the first frame: color roles, a spacing scale, a radius scale, and a type scale (sizes, and font families). In a design tool, create them as the tool's variables and reference them in every value, and make anything that repeats (a row, a button, a bar) a component. After rendering, count the values that are still literal, by kind, and turn each into a token or name why it is an exception.",
|
|
714
|
+
dont: "Hard-code one-off values for things the system already defines, or define tokens for color only and leave spacing, radius, and type size literal.",
|
|
715
|
+
why: "Tokens keep changes consistent and reviewable; values that are not tokens drift from frame to frame and state to state.",
|
|
716
|
+
basis: "W3C Design Tokens Community Group; ryux four-problems baseline 2026-10-16: in all 12 runs, with or without RYUX, spacing, radius, and font size were 100% literal",
|
|
717
|
+
check: "count literal values after render; review",
|
|
718
718
|
},
|
|
719
719
|
{
|
|
720
720
|
num: 5,
|
|
@@ -94,11 +94,17 @@ export const DESIGN_BODY = `# RYUX Design
|
|
|
94
94
|
Direction (RX-UI-12, RX-UI-10). On task UI, follow conventions and the design system.
|
|
95
95
|
5. **Read the modules the task needs** from the task table, for example ux, interaction, forms, and
|
|
96
96
|
content for a form, or ui and responsive for a layout.
|
|
97
|
-
6. **Design in the tool.** Use the Figma MCP or the pen.dev MCP.
|
|
98
|
-
(
|
|
97
|
+
6. **Design in the tool.** Use the Figma MCP or the pen.dev MCP. System first: look for the design
|
|
98
|
+
system that exists (DESIGN.md, a tokens or theme file in the repo, the design file's variables and
|
|
99
|
+
components, a linked library) and build from it. Create only what is missing: variables for color
|
|
100
|
+
roles, spacing, radius, and type sizes and families before the first frame, and components for
|
|
101
|
+
repeated elements (RX-DS-03). Building the system does not replace the states: design every
|
|
102
|
+
state that matters (empty, loading, error, including content that fails to load) and every width
|
|
103
|
+
you claim, and source assets on purpose (RX-UI-13).
|
|
99
104
|
7. **Render and inspect.** Screenshot what you made, check it against the direction and the rules,
|
|
100
|
-
fix, and render again (capabilities/qa.md).
|
|
101
|
-
|
|
105
|
+
fix, and render again (capabilities/qa.md). Count the values still literal (color, spacing,
|
|
106
|
+
radius, type size) and turn each into a variable or name the exception. List where the render
|
|
107
|
+
differs from the Design Direction (hierarchy, states, widths) and fix or record each one. Check the result against the Liveliness you set, and
|
|
102
108
|
put each prominent or decorative element through the purpose gate (RX-AS-05: purpose,
|
|
103
109
|
contribution, necessity); on calm task UI, deliberate restraint is a valid answer. Then judge the main state as a
|
|
104
110
|
reviewer would, not as a checklist: rate Clarity, Hierarchy, and Specificity from the Design Read
|
|
@@ -524,8 +530,11 @@ Does not cover: component reuse and tokens (see ryux-design-system).`,
|
|
|
524
530
|
**Quality Locks** to keep across the product: spacing, typography, color roles, terminology,
|
|
525
531
|
components, interaction patterns and states, responsive behavior, and visual hierarchy.
|
|
526
532
|
|
|
527
|
-
|
|
528
|
-
|
|
533
|
+
Look for the existing system before defining one: DESIGN.md, a tokens or theme file in the repo, the
|
|
534
|
+
design file's variables and components, or a linked library. If the project has no system yet, define
|
|
535
|
+
the smallest token set the screen needs and use it consistently: color roles, a spacing scale, a
|
|
536
|
+
radius scale, and a type scale, not color alone. After rendering, count the literal values left and
|
|
537
|
+
convert them (RX-DS-03). Do not introduce a component library the project does not use.`,
|
|
529
538
|
accessibility: `Accessibility is product quality, not an enhancement. Check:
|
|
530
539
|
|
|
531
540
|
- **Semantics**: real headings, landmarks, lists, buttons, links, labels.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryuxdsgn/design-intelligence",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.1",
|
|
4
4
|
"description": "RYUX: design intelligence for AI agents and designers. One skill, five entry points: Analyze, Design, Build, Critique, and QA, for Claude Code, Codex, Cursor, Gemini CLI, OpenCode, Cline, Copilot, and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|