@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 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: `Which agents do you use?${keys("↑/↓ move", "Space select", "a all", "Enter continue", "Ctrl+C cancel")}`,
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 = Boolean(m);
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: `Set up project context now? Every question can be skipped.${keys(YES_NO, "Enter continue")}`, initialValue: true });
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"}?${keys(YES_NO, "Enter confirm")}` });
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
- /** A choice whose value is empty means "not decided": the field stays unknown. */
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: "What must RYUX keep or support?", multi: true, options: [
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: "Web", label: "Web" },
368
- { value: "Responsive web", label: "Responsive web", hint: "one site for phone and desktop" },
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
- { value: "Existing design system", label: "Existing design system" },
371
- { value: "Existing brand identity", label: "Existing brand identity" },
372
- { value: "Accessibility, WCAG 2.2 AA", label: "Accessibility", hint: "WCAG 2.2 AA" },
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
- /** A dim second line under a prompt's question: which keys move, select, and what Enter does here. */
476
- function keys(...parts) {
477
- return `\n${pc.gray("│")} ${pc.dim(parts.join(" · "))}`;
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: `${question}${keys("↑/↓ move", "Enter choose and go on")}`,
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: `${question}${keys(TEXT_KEYS)}`, placeholder: "Enter to skip" });
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: `${question}${keys("↑/↓ move", "Space select", "Enter go on (nothing selected skips)")}`,
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: `${question}${keys(TEXT_KEYS)}`, placeholder: "Enter to skip" });
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
- const v = ask.multi ? await askMany(ask.question, ask.options, current, ask.max) : await askOne(ask.question, ask.options, current);
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: `In one sentence, what does it do?${keys(TEXT_KEYS)}`, placeholder: "Enter to skip" });
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. Design every state that matters
98
- (empty, loading, error) and every width you claim, and source assets on purpose (RX-UI-13).
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). List where the render differs from the Design
101
- Direction (hierarchy, states, widths) and fix or record each one. Check the result against the Liveliness you set, and
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
- If the project has no system yet, define the smallest token set the screen needs and use it
528
- consistently. Do not introduce a component library the project does not use.`,
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.7.1",
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": {