synthesisui 0.16.356 → 0.16.358

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.
@@ -9,7 +9,7 @@ import { readRequests } from "../doctor/requests.js";
9
9
  import { declaredReference } from "../group-role.js";
10
10
  import { CHECKER_SINCE, installedBehind, MATERIALISER_SINCE, READER_SINCE, } from "../install-marks.js";
11
11
  import { measuredScope } from "../measured-scope.js";
12
- import { body } from "../output.js";
12
+ import { body, bodyWrapped, paint, section, snippet } from "../output.js";
13
13
  import { SKILLS } from "../skills.js";
14
14
  /**
15
15
  * QUAL CLI MEDIU O CENSO EM DISCO - e era o `reader`, um inteiro, até 11/08.
@@ -625,8 +625,38 @@ export async function nextStepFor(root) {
625
625
  * chutá-lo seria pior que pedir para olhar.
626
626
  */
627
627
  return measured
628
- ? "This repo has been measured and has no system installed here. If you already imported, the system is in your account: `synthesisui list --mine`, then `synthesisui add <slug>`."
629
- : 'This repo has no design system contract yet. Ask me: "import my design system."';
628
+ ? {
629
+ sentence: "This repo has been measured and has no system installed here. If you already imported, the system is in your account: `synthesisui list --mine`, then `synthesisui add <slug>`.",
630
+ headline: "Your system is already in your account. Bring it in here:",
631
+ run: ["synthesisui list --mine", "synthesisui add <slug>"],
632
+ why: "Re-measuring would cost minutes and change nothing the platform already knows.",
633
+ }
634
+ : {
635
+ sentence: 'This repo has no design system contract yet. Ask me: "import my design system."',
636
+ headline: "Open a new agent session in this repo and say:",
637
+ say: "import my design system",
638
+ why: "I read what is already in your code - the colours, the type, the shapes, the components. Nothing is invented.",
639
+ };
640
+ }
641
+ /**
642
+ * A AÇÃO RECOMENDADA, DESENHADA PARA SER VISTA - uma, com título, o literal copiável e o por quê.
643
+ *
644
+ * A hierarquia usa a paleta que já existe e no papel que ela declara: `blue` marca o que é VIVO (o
645
+ * que ele diz ou roda), `dim` é prosa secundária (o por quê e a exigência de sessão nova). A seção
646
+ * traz as linhas em branco de graça, que é o "respiro" pedido.
647
+ */
648
+ export function renderNextStep(next, freshSession) {
649
+ const lines = [section("Do this next"), body(next.headline), ""];
650
+ if (next.say)
651
+ lines.push(paint.blue(snippet([next.say])), "");
652
+ if (next.run?.length)
653
+ lines.push(paint.blue(snippet(next.run)), "");
654
+ lines.push(...bodyWrapped(next.why).map(paint.dim));
655
+ if (freshSession)
656
+ lines.push(...bodyWrapped(freshSession.mcp
657
+ ? "A new session is what loads the skills and tools just installed, and the project's tools ask for approval once - say yes."
658
+ : "A new session is what loads the skills just installed.").map(paint.dim));
659
+ return lines.join("\n");
630
660
  }
631
661
  /**
632
662
  * A CAUDA DE UM COMANDO QUE TERMINOU: o que ainda está fora, ou nada.
@@ -642,10 +672,8 @@ export async function reportWhatIsLeft(root, opts = {}) {
642
672
  const items = await misalignments(root, opts).catch(() => []);
643
673
  if (items.length === 0) {
644
674
  const next = await nextStepFor(root).catch(() => null);
645
- if (next) {
646
- console.log("");
647
- console.log(body(next));
648
- }
675
+ if (next)
676
+ console.log(renderNextStep(next, opts.freshSession));
649
677
  return;
650
678
  }
651
679
  console.log("");
@@ -673,5 +701,5 @@ export async function align(opts) {
673
701
  */
674
702
  const next = await nextStepFor(root).catch(() => null);
675
703
  if (next)
676
- console.log(next);
704
+ console.log(next.sentence);
677
705
  }
@@ -153,7 +153,13 @@ version) {
153
153
  if (next !== current) {
154
154
  const was = pinnedInHook(current);
155
155
  console.log("");
156
- console.log(body(`Updating the terminal check in ${rc} - ${was ? `it was pinned to ${was}` : "it was on an unpinned version"}, moving it to ${version}.`));
156
+ /**
157
+ * DITA, E EM SEGUNDO PLANO. O dono pediu em 03/09 que ela saísse da tela; ela FICA porque
158
+ * reporta uma escrita FORA do repositório, e "um `.zshrc` que muda sem uma palavra é a
159
+ * definição de invasivo" é decisão dele mesmo, de 11/08. O que muda é o peso: cinza claro,
160
+ * como toda prosa secundária, para ela informar sem disputar com a ação recomendada.
161
+ */
162
+ console.log(body(paint.dim(`Updating the terminal check in ${rc} - ${was ? `it was pinned to ${was}` : "it was on an unpinned version"}, moving it to ${version}.`)));
157
163
  await writeFile(rc, next, "utf8").catch(() => { });
158
164
  }
159
165
  return;
@@ -379,18 +385,19 @@ export async function connect(opts) {
379
385
  : `✓ ${skill.label.padEnd(22)} updated to this CLI's pipeline`));
380
386
  }
381
387
  /**
382
- * O CI: escrito só quando pedido, e OFERECIDO sempre - com o que ele faz em duas linhas, porque
383
- * "adicione CI" sem dizer que nada reprova dívida herdada não é uma oferta, é um risco.
388
+ * O CI: escrito quando pedido, e NÃO MAIS OFERECIDO aqui.
389
+ *
390
+ * A oferta era verdadeira e estava no lugar errado: ela ocupava o espaço imediatamente antes da
391
+ * ação recomendada, num repositório onde a pessoa ainda não tem sistema nenhum - então o produto
392
+ * sugeria um portão de PR para uma dívida que ainda não existe. Ela mora agora na documentação do
393
+ * `connect` (dono, 03/09: "vamos deixar isso para uma documentação dentro do synthesisui").
394
+ *
395
+ * `--ci` continua fazendo tudo que fazia: quem pede, recebe.
384
396
  */
385
397
  if (opts.ci) {
386
398
  console.log("");
387
399
  await ci({ dir: root, write: true });
388
400
  }
389
- else {
390
- console.log("");
391
- console.log(body(paint.dim("Your PRs can carry this too: annotations on the exact line, and a check that fails only when the count goes UP - never on the debt you already have.")));
392
- console.log(paint.blue(snippet(["npx synthesisui@latest ci"])));
393
- }
394
401
  /** A mesma versão que a fiação do editor recebe - os dois pinam no mesmo número. */
395
402
  await offerShellHook(opts.shell === true, opts.version);
396
403
  /**
@@ -409,16 +416,13 @@ export async function connect(opts) {
409
416
  const needsRestart = moved(wired.hook) ||
410
417
  moved(wired.session) ||
411
418
  (Array.isArray(wired.mcp) && wired.mcp.some((m) => moved(m.status)));
412
- if (needsRestart) {
413
- console.log("");
414
- console.log(body("Reopen your editor session - all of them are read at startup."));
415
- if (want.mcp &&
416
- Array.isArray(wired.mcp) &&
417
- wired.mcp.some((m) => moved(m.status))) {
418
- console.log(body('A project MCP server needs approving once; say yes when it asks. Then "/mcp" lists synthesisui.'));
419
- }
420
- }
421
- await reportWhatIsLeft(root, { cli: opts.version });
419
+ const mcpMoved = Boolean(want.mcp &&
420
+ Array.isArray(wired.mcp) &&
421
+ wired.mcp.some((m) => moved(m.status)));
422
+ await reportWhatIsLeft(root, {
423
+ cli: opts.version,
424
+ ...(needsRestart ? { freshSession: { mcp: mcpMoved } } : {}),
425
+ });
422
426
  /**
423
427
  * O CUSTO DO HOOK, DITO SEM UM NÚMERO QUE NÃO É NOSSO.
424
428
  *
@@ -434,10 +438,10 @@ export async function connect(opts) {
434
438
  */
435
439
  if (wired.command.startsWith("npx synthesisui@")) {
436
440
  console.log("");
437
- console.log(body("The hook runs through npx, which resolves this package against the"));
438
- console.log(body("registry on every edit - that wait is the network, not the check itself"));
439
- console.log(body("(the analysis is about 80ms). Adding synthesisui to your devDependencies"));
440
- console.log(body("makes npx resolve it locally instead, which is several times faster;"));
441
- console.log(body("run this again afterwards and it will switch by itself."));
441
+ console.log(body(paint.dim("The hook runs through npx, which resolves this package against the")));
442
+ console.log(body(paint.dim("registry on every edit - that wait is the network, not the check itself")));
443
+ console.log(body(paint.dim("(the analysis is about 80ms). Adding synthesisui to your devDependencies")));
444
+ console.log(body(paint.dim("makes npx resolve it locally instead, which is several times faster;")));
445
+ console.log(body(paint.dim("run this again afterwards and it will switch by itself.")));
442
446
  }
443
447
  }
@@ -59,23 +59,49 @@ const segments = (name) => name.replace(/^--/, "").split("-");
59
59
  *
60
60
  * 1. quantos segmentos ele compartilha com o nosso token daquela família
61
61
  * `--ds-radius-xs` puxa `--radius-xs` (2) e não `--spacing` (0)
62
- * 2. a convenção dominante dele - o primeiro segmento mais frequente entre os tokens dele
62
+ * 2. quantos segmentos dele são um PAPEL que este sistema declara - ver `roles`
63
+ * 3. a convenção dominante dele - o primeiro segmento mais frequente entre os tokens dele
63
64
  * no repo real, `--color-*` (57) contra `--dashboard-*` (25)
64
- * 3. ordem de declaração, que é o desempate que sempre existe e nunca inventa
65
+ * 4. ordem de declaração, que é o desempate que sempre existe e nunca inventa
66
+ *
67
+ * O CRITÉRIO 2 NASCEU DE UMA DECISÃO DO DONO (03/09), e o critério 1 não alcançava o caso. No
68
+ * sistema dele `#f59e0b` é `--color-tier-gold` E `--color-feedback-warning`, e medindo o censo:
69
+ * `tier-gold` tem 59 menções contra 7, e uma escada própria (bronze 13, prata 13). Pela contagem
70
+ * bruta ele venceria - e vencia, sempre que o nosso token daquele valor não existisse para o
71
+ * critério 1 comparar: aí caía na ORDEM DE DECLARAÇÃO, e `tier-gold` vem antes no CSS dele.
72
+ *
73
+ * A ESCOLHA NÃO É PELO MAIS FREQUENTE, É PELO ERRO QUE SE ANUNCIA. `feedback-warning` escrito num
74
+ * badge de nível mostra "warning" onde devia ser ouro, e quem lê corrige. `tier-gold` escrito num
75
+ * alerta pinta a cor CERTA com o nome de outro domínio - a tela fica visualmente correta e ninguém
76
+ * corrige nunca. Entre dois nomes dele, o que descreve um PAPEL erra alto; o que descreve um
77
+ * domínio de negócio erra calado.
65
78
  */
66
- function pick(candidates, ours, convention) {
79
+ function pick(candidates, ours, convention,
80
+ /**
81
+ * OS PAPÉIS QUE ESTE SISTEMA DECLARA - o critério 2, e o único que decide quando não há token
82
+ * nosso daquele valor para comparar.
83
+ *
84
+ * DERIVADO, nunca escrito: são os segmentos finais dos papéis semânticos do próprio sistema, e
85
+ * por isso a regra vale em qualquer projeto. Um projeto que chame o aviso de `atencao` puxa
86
+ * `atencao`; nenhum nome de família deste ou daquele repositório entra aqui (`INV-GERAL-07`).
87
+ */
88
+ roles) {
67
89
  const mine = ours ? new Set(segments(ours)) : null;
68
90
  let best = candidates[0];
69
- let bestScore = [-1, -1];
91
+ let bestScore = [-1, -1, -1];
70
92
  for (const name of candidates) {
71
93
  const parts = segments(name);
72
94
  const shared = mine ? parts.filter((p) => mine.has(p)).length : 0;
73
95
  const score = [
74
96
  shared,
97
+ parts.filter((p) => roles.has(p)).length,
75
98
  convention.get(parts[0] ?? "") ?? 0,
76
99
  ];
77
100
  if (score[0] > bestScore[0] ||
78
- (score[0] === bestScore[0] && score[1] > bestScore[1])) {
101
+ (score[0] === bestScore[0] && score[1] > bestScore[1]) ||
102
+ (score[0] === bestScore[0] &&
103
+ score[1] === bestScore[1] &&
104
+ score[2] > bestScore[2])) {
79
105
  best = name;
80
106
  bestScore = score;
81
107
  }
@@ -106,14 +132,29 @@ function pick(candidates, ours, convention) {
106
132
  * sistema do dono, `#f59e0b` é `--color-tier-gold` E `--color-feedback-warning` - dois conceitos
107
133
  * dele, gamificação e estado -, e o relatório dizia `→ {color.tier-gold}` como se fosse fato.
108
134
  */
109
- function escolha(candidates, ours, convention) {
110
- const name = pick(candidates, ours, convention);
135
+ function escolha(candidates, ours, convention, roles) {
136
+ const name = pick(candidates, ours, convention, roles);
111
137
  return { name, also: candidates.filter((c) => c !== name) };
112
138
  }
113
139
  export function theirNames(ours, theirs) {
114
140
  const out = new Map();
115
141
  if (theirs.byName.size === 0)
116
142
  return out;
143
+ /**
144
+ * OS PAPÉIS QUE ESTE SISTEMA DECLARA, derivados dos NOSSOS nomes - ver o critério 2 de `pick`.
145
+ *
146
+ * O último segmento de cada token semântico é o papel: `--ds-color-semantic-warning` dá
147
+ * `warning`. Sai daqui e não de uma lista escrita porque uma lista seria o nosso vocabulário
148
+ * virando régua - um sistema que chame o aviso de `atencao` tem que puxar `atencao`.
149
+ */
150
+ const roles = new Set();
151
+ for (const name of ours.byName.keys()) {
152
+ if (!name.includes("-semantic-"))
153
+ continue;
154
+ const last = segments(name).at(-1);
155
+ if (last)
156
+ roles.add(last);
157
+ }
117
158
  /** A convenção dominante dele, contada e não suposta - ver `pick`. */
118
159
  const convention = new Map();
119
160
  for (const name of theirs.byName.keys()) {
@@ -151,7 +192,7 @@ export function theirNames(ours, theirs) {
151
192
  : candidates.filter((n) => familySays(kind, n));
152
193
  if (usable.length === 0)
153
194
  continue;
154
- out.set(key, escolha(usable, name, convention));
195
+ out.set(key, escolha(usable, name, convention, roles));
155
196
  }
156
197
  }
157
198
  for (const [value, candidates] of theirs.byValue) {
@@ -161,7 +202,7 @@ export function theirNames(ours, theirs) {
161
202
  const key = `${kind}:${value}`;
162
203
  if (out.has(key))
163
204
  continue;
164
- out.set(key, escolha(candidates, null, convention));
205
+ out.set(key, escolha(candidates, null, convention, roles));
165
206
  }
166
207
  }
167
208
  return out;
package/dist/output.js CHANGED
@@ -20,6 +20,34 @@ export function snippet(lines) {
20
20
  export function body(line) {
21
21
  return ` ${line}`;
22
22
  }
23
+ /**
24
+ * PROSA LONGA QUEBRADA NA MESMA LARGURA QUE O RESTO DA TELA RESPEITA.
25
+ *
26
+ * Todo parágrafo daqui foi quebrado À MÃO até agora, uma string por linha - o que funciona para
27
+ * texto fixo e não funciona para texto que vem de dado. Medido em 03/09 na ação recomendada do
28
+ * `connect`: a linha do "por quê" saiu com 103 colunas num desenho de 66, e num terminal estreito
29
+ * ela dobra no meio de uma palavra, exatamente na frase que a pessoa mais precisa ler.
30
+ *
31
+ * Quebra por PALAVRA e nunca no meio de uma; uma palavra maior que a largura fica sozinha na linha
32
+ * em vez de ser cortada. Já indenta como `body`, porque a indentação conta para a largura.
33
+ */
34
+ export function bodyWrapped(text, width = WIDTH) {
35
+ const lines = [];
36
+ let current = "";
37
+ for (const word of text.split(/\s+/).filter(Boolean)) {
38
+ if (!current)
39
+ current = word;
40
+ else if (`${current} ${word}`.length <= width)
41
+ current += ` ${word}`;
42
+ else {
43
+ lines.push(current);
44
+ current = word;
45
+ }
46
+ }
47
+ if (current)
48
+ lines.push(current);
49
+ return lines.map(body);
50
+ }
23
51
  /**
24
52
  * Colour, used the way the product uses motion: a quiet base and a few
25
53
  * deliberate accents (dono, 30/07: "um cinza mais escuro e um mais claro,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.356",
3
+ "version": "0.16.358",
4
4
  "description": "Bring SynthesisUI design systems into any project - tokens, typed components, whole pages and an agent-ready CLAUDE.md manifest.",
5
5
  "type": "module",
6
6
  "bin": {