synthesisui 0.16.235 → 0.16.238

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.
@@ -250,15 +250,13 @@ export async function add(slug, opts) {
250
250
  await writeFile(join(slugDir, "requires.json"), `${JSON.stringify(required, null, 2)}\n`, "utf8");
251
251
  }
252
252
  /** A filosofia continua sendo CONTADA na saída, e ela vive no documento - ver abaixo. */
253
- const philosophy = payload.document.philosophy;
254
- const sections = philosophy?.sections ?? [];
253
+ const voice = payload.voice ?? { hasContext: false, sections: 0 };
255
254
  /**
256
- * 5c. A FILOSOFIA NÃO É MATERIALIZADA - ela já está aqui.
257
- *
258
- * `philosophy` é campo do DOCUMENTO versionado (`design-system.ts`), e o `design-system.json`
259
- * escrito no passo 2 a carrega inteira. O `philosophy.md` era uma segunda cópia do mesmo dado, no
260
- * mesmo commit, que só era reescrita no upgrade seguinte - dois caminhos para uma verdade é como
261
- * um deles começa a discordar do outro. O `system_doctrine` lê do documento.
255
+ * 5c. A VOZ NÃO É MATERIALIZADA - nem no `.md` (aposentado acima) nem no
256
+ * `design-system.json` (R2, 16/08: o payload viaja sem `philosophy`). Ela
257
+ * mora na plataforma e o `system_doctrine` a busca servida - sempre a
258
+ * versão atual, nada em texto plano no repo. As REGRAS continuam locais
259
+ * (`doctrine.json`): o doctor e o hook precisam delas offline.
262
260
  */
263
261
  // 6. discovery by the agent
264
262
  const claudeMd = await syncClaudeMd(projectRoot);
@@ -288,8 +286,8 @@ export async function add(slug, opts) {
288
286
  */
289
287
  const doctrine = [
290
288
  ...(rules.length > 0 ? [`${rules.length} rule(s)`] : []),
291
- ...(sections.length > 0 || philosophy?.context
292
- ? [`${sections.length} section(s) of philosophy`]
289
+ ...(voice.sections > 0 || voice.hasContext
290
+ ? [`the voice (${voice.sections} section(s), served from the platform)`]
293
291
  : []),
294
292
  ];
295
293
  if (doctrine.length > 0)
@@ -59,14 +59,21 @@ export async function component(slug, name, opts) {
59
59
  if (!SAFE_NAME.test(res.name)) {
60
60
  throw new RegistryError(`Registry returned an unsafe component name.`);
61
61
  }
62
- const dir = join(root, "_synthesisui", "ds", slug, "components");
63
- await mkdir(dir, { recursive: true });
64
- await writeFile(join(dir, `${res.name}.json`), `${JSON.stringify(res.recipe, null, 2)}\n`, "utf8");
65
- await writeFile(join(dir, `${res.name}.css`), `${res.css}\n`, "utf8");
66
- console.log(`✓ ${res.name} → _synthesisui/ds/${slug}/components/${res.name}.{json,css} (${slug} v${res.version})`);
62
+ // R3 (dono, 16/08): as cópias .json/.css em _synthesisui só nascem quando
63
+ // são o PRODUTO do comando - `--artifacts-only` e targets não-Next. Quando
64
+ // o .tsx é materializado logo abaixo, ninguém as lê depois (medido na
65
+ // auditoria de 16/08), e cada arquivo a mais no repo dele é superfície.
66
+ const config = await readProjectConfig(root);
67
+ const artifactsAreTheProduct = opts.artifactsOnly === true || config.target !== "next";
68
+ if (artifactsAreTheProduct) {
69
+ const dir = join(root, "_synthesisui", "ds", slug, "components");
70
+ await mkdir(dir, { recursive: true });
71
+ await writeFile(join(dir, `${res.name}.json`), `${JSON.stringify(res.recipe, null, 2)}\n`, "utf8");
72
+ await writeFile(join(dir, `${res.name}.css`), `${res.css}\n`, "utf8");
73
+ console.log(`✓ ${res.name} → _synthesisui/ds/${slug}/components/${res.name}.{json,css} (${slug} v${res.version})`);
74
+ }
67
75
  // 2. YOUR component - a real, importable `export function <Pascal>()` in the
68
76
  // project's flavor (config: styles css|tailwind), under componentsDir.
69
- const config = await readProjectConfig(root);
70
77
  const wantInteractive = opts.interactive && hasInteractiveTemplate(res.name);
71
78
  if (!opts.artifactsOnly && config.target === "next") {
72
79
  /**
@@ -55,17 +55,21 @@ export async function generate(description, opts) {
55
55
  }
56
56
  console.log(`→ generating a component for "${slug}" at ${base} …`);
57
57
  const res = await postGenerate(base, { slug, description, name: opts.name });
58
- const dir = join(root, "_synthesisui", "ds", slug, "generated");
59
- await mkdir(dir, { recursive: true });
60
- await writeFile(join(dir, `${res.name}.json`), `${JSON.stringify(res.recipe, null, 2)}\n`, "utf8");
61
- await writeFile(join(dir, `${res.name}.css`), `${res.css}\n`, "utf8");
62
58
  const tries = `${res.tries} ${res.tries === 1 ? "try" : "tries"}`;
63
59
  console.log(`✓ ${res.name} generated (${res.model}, ${tries})`);
64
- console.log(` → _synthesisui/ds/${slug}/generated/${res.name}.{json,css}`);
65
60
  // Materialize YOUR component (.tsx) too - the SAME codegen `component` uses -
66
61
  // so a generated component is as usable as a brought-in one, not just a
67
62
  // recipe you have to wire by hand.
68
63
  const config = await readProjectConfig(root);
64
+ // R3 (dono, 16/08): as cópias .json/.css só quando são o produto - num
65
+ // target não-Next o .tsx abaixo não nasce, e aí elas são a entrega.
66
+ if (config.target !== "next") {
67
+ const dir = join(root, "_synthesisui", "ds", slug, "generated");
68
+ await mkdir(dir, { recursive: true });
69
+ await writeFile(join(dir, `${res.name}.json`), `${JSON.stringify(res.recipe, null, 2)}\n`, "utf8");
70
+ await writeFile(join(dir, `${res.name}.css`), `${res.css}\n`, "utf8");
71
+ console.log(` → _synthesisui/ds/${slug}/generated/${res.name}.{json,css}`);
72
+ }
69
73
  let materialized = false;
70
74
  if (config.target === "next") {
71
75
  const version = await readActiveVersion(root, slug);
@@ -109,6 +109,25 @@ const TOOLS = [
109
109
  required: ["name"],
110
110
  },
111
111
  },
112
+ {
113
+ name: "playbook",
114
+ description: "The skill playbooks (init, import, adapt) - SERVED, not shipped, so they are always current and your context only carries the step you are on. Call with { skill } to get the framing and a table of contents; then fetch ONLY the chapter for your current step with { skill, section }. Never fetch more than the step needs.",
115
+ inputSchema: {
116
+ type: "object",
117
+ properties: {
118
+ skill: {
119
+ type: "string",
120
+ enum: ["init", "import", "adapt"],
121
+ description: "Which playbook.",
122
+ },
123
+ section: {
124
+ type: "string",
125
+ description: "A chapter id from the toc. Omit to get the framing + toc.",
126
+ },
127
+ },
128
+ required: ["skill"],
129
+ },
130
+ },
112
131
  {
113
132
  name: "recipe_vocabulary",
114
133
  description: "What a recipe CAN hold: every state that compiles, every preview form, the floor per kind, and the rules a media region needs. Call this BEFORE writing a recipe, not after - the reader that skipped it sent a `dark:` inside a variant and a state the compiler cannot spell, and both were dropped in silence. Served from the catalogue, so it grows as the contract grows.",
@@ -297,18 +316,55 @@ async function findToken(root, value) {
297
316
  * palavra que a pessoa já tem. Sem rede, a doutrina responde igual e a checagem é que se cala: o
298
317
  * dado é local justamente para não depender de servidor nenhum.
299
318
  */
319
+ /** A voz do sistema, servida da plataforma - ver o comentário no chamador. */
320
+ async function fetchVoice(root) {
321
+ const slug = await installedSlug(root);
322
+ const token = await readToken();
323
+ if (!slug || !token)
324
+ return { kind: "unreachable" };
325
+ const res = await fetch(`${resolveRegistry()}/api/registry/ds/${slug}?voice=1`, { headers: { Authorization: `Bearer ${token}` } }).catch(() => null);
326
+ if (!res?.ok)
327
+ return { kind: "unreachable" };
328
+ const body = (await res.json().catch(() => null));
329
+ if (!body)
330
+ return { kind: "unreachable" };
331
+ return {
332
+ kind: "ok",
333
+ voice: {
334
+ context: body.context ?? undefined,
335
+ sections: body.sections ?? [],
336
+ },
337
+ };
338
+ }
300
339
  async function systemDoctrine(root) {
301
340
  const { documents, doctrines } = await loadSystem(root);
302
341
  const rules = doctrines.flatMap((d) => d.rules);
303
342
  const parts = [];
304
343
  const pinned = doctrines[0]?.version;
305
- for (const doc of documents) {
306
- const p = doc.philosophy;
307
- if (p?.context)
344
+ /**
345
+ * A VOZ É SERVIDA, NÃO LIDA DO DISCO (R2, dono 16/08). As REGRAS continuam
346
+ * locais - o doctor e o hook precisam delas offline, e essa decisão está
347
+ * declarada três vezes. A voz nunca foi lida por nenhum loop local: só esta
348
+ * ferramenta a renderiza, e ela agora pergunta à plataforma - sempre a
349
+ * versão atual, e nada dela em texto plano no repo. Sem rede, as regras
350
+ * respondem igual e a voz se declara indisponível em vez de sumir calada.
351
+ *
352
+ * Installs antigos (design-system.json com `philosophy` dentro) continuam
353
+ * funcionando: o disco é o fallback quando a rede não responde.
354
+ */
355
+ const localVoice = documents.map((doc) => doc.philosophy ?? {});
356
+ const served = await fetchVoice(root);
357
+ const voices = served.kind === "ok"
358
+ ? [served.voice]
359
+ : localVoice.filter((p) => p.context || (p.sections?.length ?? 0) > 0);
360
+ for (const p of voices) {
361
+ if (p.context)
308
362
  parts.push(`## What this product is\n\n${p.context}`);
309
- for (const sec of p?.sections ?? [])
363
+ for (const sec of p.sections ?? [])
310
364
  parts.push(`## ${sec.title}\n\n${sec.body}`);
311
365
  }
366
+ if (served.kind === "unreachable" && voices.length === 0)
367
+ parts.push("## Voice\n\nThe voice is served from the platform and could not be reached right now. The rules above still apply in full - build with them, and fetch the voice again before writing user-facing copy.");
312
368
  if (rules.length === 0 && parts.length === 0)
313
369
  return "This system carries no rules and no philosophy yet. Nothing here overrides your judgement - build with its tokens and its components, and say what you needed and could not find.";
314
370
  const head = [
@@ -408,6 +464,28 @@ async function listComponents(root) {
408
464
  * properties need to be dynamic - the more we map, the more precision". A list written
409
465
  * into a published package is a list that stops matching the contract.
410
466
  */
467
+ /**
468
+ * O PLAYBOOK, na fatia pedida (R1, dono 16/08): sem `section`, a moldura + o
469
+ * sumário; com, UM capítulo. O corpo inteiro nunca viaja - a dieta de contexto
470
+ * é servida por construção.
471
+ */
472
+ async function playbook(skill, section) {
473
+ const answer = await askCatalogue("playbook", undefined, section ? { skill, section } : { skill });
474
+ if (!answer.ok)
475
+ return answer.because;
476
+ const body = answer.body;
477
+ if ("error" in body)
478
+ return [body.error, body.hint].filter(Boolean).join("\n");
479
+ if ("sections" in body) {
480
+ return [
481
+ body.intro,
482
+ "",
483
+ "## Chapters - fetch ONLY the one for your current step",
484
+ ...body.sections.map((c) => `- ${c.id} - ${c.title}`),
485
+ ].join("\n");
486
+ }
487
+ return body.body;
488
+ }
411
489
  async function recipeVocabulary() {
412
490
  const answer = await askCatalogue("vocabulary");
413
491
  if (!answer.ok)
@@ -769,7 +847,9 @@ cli) {
769
847
  }
770
848
  })();
771
849
  }
772
- async function askCatalogue(want, post) {
850
+ async function askCatalogue(want, post,
851
+ /** Parâmetros extras do GET (ex.: o playbook e o capítulo pedidos). */
852
+ query) {
773
853
  const token = await readToken();
774
854
  if (!token) {
775
855
  return {
@@ -779,7 +859,8 @@ async function askCatalogue(want, post) {
779
859
  }
780
860
  const base = resolveRegistry();
781
861
  try {
782
- const res = await fetch(`${base}/api/catalogue${post ? "" : `?want=${want}`}`, post
862
+ const extra = query ? `&${new URLSearchParams(query).toString()}` : "";
863
+ const res = await fetch(`${base}/api/catalogue${post ? "" : `?want=${want}${extra}`}`, post
783
864
  ? {
784
865
  method: "POST",
785
866
  headers: {
@@ -966,6 +1047,11 @@ cli) {
966
1047
  case "add_component":
967
1048
  countAgentRead(root, String(args.name ?? ""), "add", cli);
968
1049
  return text(await addComponent(root, String(args.name ?? "")));
1050
+ case "playbook": {
1051
+ const skill = String(args?.skill ?? "");
1052
+ const section = args?.section ? String(args.section) : undefined;
1053
+ return text(await playbook(skill, section));
1054
+ }
969
1055
  case "recipe_vocabulary":
970
1056
  return text(await recipeVocabulary());
971
1057
  case "validate_recipe": {
@@ -123,12 +123,15 @@ export async function refit(file, opts) {
123
123
  recipe: res.recipe,
124
124
  });
125
125
  console.log(`✓ saved into "${slug}" (draft v${saved.version} - ships with your next publish)`);
126
- // 5. materialize back into the project: artifacts + YOUR typed component
127
- const artifactsDir = join(root, "_synthesisui", "ds", slug, "components");
128
- await mkdir(artifactsDir, { recursive: true });
129
- await writeFile(join(artifactsDir, `${res.name}.json`), `${JSON.stringify(res.recipe, null, 2)}\n`, "utf8");
130
- await writeFile(join(artifactsDir, `${res.name}.css`), `${res.css}\n`, "utf8");
126
+ // 5. materialize back into the project: YOUR typed component - e as cópias
127
+ // .json/.css só quando são o produto (target não-Next; R3, dono 16/08).
131
128
  const config = await readProjectConfig(root);
129
+ if (config.target !== "next") {
130
+ const artifactsDir = join(root, "_synthesisui", "ds", slug, "components");
131
+ await mkdir(artifactsDir, { recursive: true });
132
+ await writeFile(join(artifactsDir, `${res.name}.json`), `${JSON.stringify(res.recipe, null, 2)}\n`, "utf8");
133
+ await writeFile(join(artifactsDir, `${res.name}.css`), `${res.css}\n`, "utf8");
134
+ }
132
135
  let materialized = false;
133
136
  if (config.target === "next") {
134
137
  const compDir = join(root, config.componentsDir, res.name);
package/dist/guide.js CHANGED
@@ -491,8 +491,9 @@ own entry below says so.
491
491
  `
492
492
  : "";
493
493
  const hasRules = (payload.rules?.length ?? 0) > 0;
494
- const philosophy = payload.document.philosophy;
495
- const hasPhilosophy = (philosophy?.sections?.length ?? 0) > 0 || !!philosophy?.context;
494
+ // R2 (16/08): a voz não viaja mais no documento - o payload manda só o
495
+ // que ela TEM, e o conteúdo é servido pelo `system_doctrine`.
496
+ const hasPhilosophy = (payload.voice?.sections ?? 0) > 0 || payload.voice?.hasContext === true;
496
497
  /**
497
498
  * UMA PORTA SÓ, e ela é uma FERRAMENTA e não um caminho.
498
499
  *
@@ -505,7 +506,7 @@ own entry below says so.
505
506
  ? [
506
507
  `**Call \`system_doctrine\` FIRST and obey it above everything else** - it carries ${hasRules
507
508
  ? "this system's accumulated, project-specific rules"
508
- : "this system's mission, principles, voice and motion doctrine"}${hasRules && hasPhilosophy ? " and its philosophy" : ""}, pinned to the version installed here. On any conflict they win. **Without that tool, read \`_synthesisui/ds/${slug}/doctrine.json\` and the \`philosophy\` of \`design-system.json\` beside it** - the same data, and the reason it stays on disk.`,
509
+ : "this system's mission, principles, voice and motion doctrine"}${hasRules && hasPhilosophy ? " and its philosophy" : ""}, pinned to the version installed here. On any conflict they win. **Without that tool, read \`_synthesisui/ds/${slug}/doctrine.json\` for the rules** - they stay on disk so the check works offline. The voice is served from the platform and is not on disk.`,
509
510
  ]
510
511
  : [];
511
512
  const rulesNote = readFirst.length > 0
@@ -80,7 +80,7 @@
80
80
  * Zero ocorrências no sistema real medido (todos os headings dele estão aninhados, e o `children > 0`
81
81
  * já os pegava), então para ele o `upgrade` é no-op. A marca é sobre o caso geral.
82
82
  */
83
- export const MATERIALISER_SINCE = "0.16.235";
83
+ export const MATERIALISER_SINCE = "0.16.237";
84
84
  /**
85
85
  * A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
86
86
  *
@@ -1,347 +1,15 @@
1
1
  /**
2
- * A SKILL DE MANUTENÇÃO, como o CLI a distribui.
2
+ * O STUB DA SKILL - o corpo é SERVIDO, não embarcado (R1, dono 16/08).
3
3
  *
4
- * Mesmo motivo do `skill-init.ts` e do `skill-import.ts`: o build é `tsc` e nada mais, então um `.md`
5
- * precisaria de um passo de cópia que pode silenciosamente não rodar. Um módulo TypeScript não pode
6
- * falhar em ser empacotado.
4
+ * O playbook inteiro morava aqui (13 KB) e, por consequência, no tarball
5
+ * público do npm e no repo de cada cliente em texto plano. Ele mudou para a
6
+ * plataforma (apps/web/src/lib/ds/playbooks.ts, gêmeo por spec do
7
+ * `.claude/skills/sui-adapt/SKILL.md`) e é servido pelo catalogue autenticado -
8
+ * a mesma doutrina de sempre: SERVED, NOT SHIPPED. O agente busca o sumário
9
+ * e depois SÓ o capítulo do passo em que está (a dieta de contexto).
7
10
  *
8
- * ESTA É A FONTE. A cópia em `.claude/skills/` é o que o nosso editor lê, e o spec assere que as duas
9
- * são idênticas.
10
- *
11
- * E ela é a TERCEIRA que o `connect` instala - as outras duas são de ENTRADA (primeira corrida,
12
- * import). Esta responde a pergunta do dia seguinte, apontando para uma tela: *"isso aqui está de
13
- * acordo com o meu design system?"*. Deixá-la fora do `connect` faria dela uma skill nossa, e o caso
14
- * de uso que a motivou é o cliente rodando sozinho toda semana.
11
+ * O frontmatter fica INTEIRO no stub: é a description que faz a skill ser
12
+ * invocada, e ela não é segredo - a esteira é.
15
13
  */
16
- export const ADAPT_SKILL = `---
17
- name: sui-adapt
18
- description: Confronta UM componente (ou uma página, ou uma pasta) contra o design system instalado e adapta o que der - cobertura de token medida, conserto mecânico proposto antes de escrever, e o que sobra classificado em "regra nova" ou "conserto local". Use quando alguém aponta para uma peça e pergunta se ela está de acordo com o sistema (ex. "/sui-adapt components/ui/card", "analisa esse componente aqui", "isso aqui segue o meu design system?"), como rotina semanal, ou logo depois de criar/alterar um componente. Mede antes de propor, propõe antes de escrever, e nunca arquiva pedido em nome de ninguém.
19
- ---
20
-
21
- # Adaptar uma peça ao sistema
22
-
23
- **Seu primeiro comando é a medição.** Não leia \`.mcp.json\`, não abra o censo, não liste
24
- arquivo: \`check_file\` ou \`doctor <alvo>\` responde tudo isso em um passo, e é o que o resto
25
- desta skill consome. Um agente que sai explorando antes gasta a paciência de quem pediu e
26
- chega ao mesmo lugar.
27
-
28
- O propósito do produto, que decide todo empate abaixo: **ler o repositório do cliente e
29
- devolver receitas com paridade visual, semântica e funcional, sem supor e sem inventar
30
- nada.**
31
-
32
- Esta skill é a ponta de manutenção. As outras duas que o cliente tem são de ENTRADA -
33
- \`sui-init\` e \`sui-import-ds\` transformam o repositório dele em sistema. Esta responde a
34
- pergunta do dia seguinte, que ele faz apontando para uma tela: *"isso aqui está de acordo
35
- com o meu design system?"*.
36
-
37
- \`CLAUDE.md\` manda. Quando os dois divergirem, este arquivo é que está velho.
38
-
39
- ---
40
-
41
- ## 0. COMO VOCÊ FALA COM ELE
42
-
43
- **Ele quer saber o que está no projeto DELE. Nada do que está do nosso lado interessa.**
44
-
45
- Esta seção vale acima de qualquer outra abaixo, porque uma medição perfeita escrita na
46
- nossa língua é uma medição que ele não lê. Palavras que **nunca** aparecem para ele:
47
-
48
- \`\`\`
49
- ledger · census · scope · reading as ADOPTION · refresh_system · check_file
50
- system_doctrine · carrier · alcançável · o número da versão do CLI · --fix --write
51
- cobertura de token · o nome de qualquer comando ou ferramenta nossa
52
- \`\`\`
53
-
54
- A tradução é sempre a mesma: fale do **arquivo**, da **propriedade**, do **valor** e da
55
- **variável dele**.
56
-
57
- \`\`\`
58
- em vez de diga
59
- o ledger.cli está em 0.16.229 sua ferramenta está em dia
60
- o scope do sistema é packages/ui este arquivo fica fora da pasta de onde o
61
- seu design system nasceu, e isso é normal
62
- 1em → var(--ds-spacing-md) o seu projeto já chama esse valor de \`--spacing-md\`
63
- cobertura de token: 0% nenhum destes valores vem do seu design system
64
- rode doctor --fix --write eu troco para você, se você quiser
65
- \`\`\`
66
-
67
- **A variável que você oferece é a DELE.** O comando já devolve o nome do vocabulário dele
68
- quando o repositório tem um - \`--color-lightgray-700\`, e não \`--ds-color-gray-400\`. Nunca
69
- reescreva a sugestão para o nosso prefixo: renomear a variável dele não é adotar o sistema.
70
-
71
- ## 0b. O QUE ESTA SKILL NÃO FAZ
72
-
73
- Três limites, e cada um existe por um motivo que já custou alguma coisa:
74
-
75
- \`\`\`
76
- não escreve sem propor é o repositório DELE. Escrever sem confirmação é outra
77
- categoria de confiança, e uma skill que perde essa
78
- confiança não é rodada uma segunda vez
79
- não arquiva pedido um pedido fila uma decisão na plataforma. A skill
80
- PERGUNTA; quem decide é ele
81
- não inventa token, nome um agente que cala um relatório fazendo o sistema crescer é
82
- nem regra pior que a deriva que ele veio medir
83
- \`\`\`
84
-
85
- ## 1. O ALVO, E POR QUE O ESCOPO É DECISÃO DA SKILL
86
-
87
- Componente quase nunca é um arquivo. \`Card.tsx\` costuma vir com \`Card.css\`, \`Card.stories.tsx\`
88
- e às vezes um \`index.ts\` - e medir só o \`.tsx\` produz um número que mente por omissão: o css
89
- ao lado é justamente onde os valores à mão se escondem.
90
-
91
- \`\`\`
92
- 1. resolva o alvo o que ele apontou, ou o arquivo aberto, ou o que ele acabou de mexer
93
- 2. suba para a PASTA quando o irmão existir (mesmo nome, extensão diferente)
94
- 3. DIGA qual escopo você usou, com o número de arquivos
95
- \`\`\`
96
-
97
- Nunca meça os dois e escolha o maior. Diga o que mediu - **com os nomes dos arquivos**, não
98
- com a palavra "escopo".
99
-
100
- **O alvo fora da pasta importada é o caso COMUM, e não um erro.** O sistema nasceu de uma
101
- pasta, e uma tela de app que consome o DS está fora dela - é literalmente o pedido
102
- *"conserta essa página pro meu design system"*. Ali as variáveis valem igual, porque elas
103
- são globais; o que não existe é receita daquele componente. Diga isso em uma linha, sem a
104
- palavra "escopo", e siga.
105
-
106
- ## 2. MEÇA - e a medida é determinística, não sua
107
-
108
- Duas portas, mesma resposta. Use a que a sessão tiver:
109
-
110
- \`\`\`
111
- MCP check_file { path }
112
- terminal npx synthesisui doctor <alvo>
113
- \`\`\`
114
-
115
- Não recalcule nada de cabeça. O número que você reporta é o que o comando disse.
116
-
117
- **A abertura tem três linhas e nenhuma a mais.** Ela responde "está em dia?", "o que você
118
- leu?" e "como estou?", nesta ordem:
119
-
120
- \`\`\`
121
- sua ferramenta está em dia
122
-
123
- li OverviewHeaderInformation - o componente e a folha de estilo dele, 2 arquivos
124
-
125
- 17 valores estão escritos à mão aqui, e nenhum vem do seu design system
126
- \`\`\`
127
-
128
- Se a ferramenta NÃO estiver em dia, essa primeira linha vira o primeiro item da fila, com o
129
- comando à vista - uma medição feita com a versão velha responde outra coisa.
130
-
131
- Os três grupos que o comando separa, e o que cada um vira:
132
-
133
- \`\`\`
134
- já usa uma variável nada a fazer
135
- o projeto já nomeia é uma troca - vira item, com a variável DELE ao lado
136
- ninguém nomeia é uma decisão dele - vira item, com as três saídas
137
- \`\`\`
138
-
139
- ## 3. MONTE A FILA, E CONTE OS ITENS ANTES DE COMEÇAR
140
-
141
- Aqui é onde esta skill se ganha ou se perde. Despejar tudo de uma vez - duas regras, quatro
142
- decisões, dez trocas - não é um relatório, é uma parede. Quem lê não tem como agir; só
143
- concordar ou fechar a aba.
144
-
145
- Junte tudo o que você achou numa fila ÚNICA e **ordene por custo**:
146
-
147
- \`\`\`
148
- 1º não muda um pixel troca por uma variável de mesmo valor
149
- 2º pode mudar o pixel \`em\` que segue a fonte, um degrau que colapsa
150
- 3º não é troca, é pedido variável nova, peça nova, regra nova
151
- \`\`\`
152
-
153
- Assim ele despacha o barato primeiro e para quando quiser, sem ficar devendo nada.
154
-
155
- Anuncie o tamanho antes do primeiro item, sempre, e em uma frase que ele leia:
156
-
157
- \`\`\`
158
- achei 8 coisas. 2 não mudam nada na tela, 4 podem mudar, e 2 são decisão sua.
159
- \`\`\`
160
-
161
- ## 4. UM ITEM POR VEZ, COM AS TRÊS SAÍDAS - e espere a resposta
162
-
163
- Nunca apresente o item 2 antes de ele responder o 1. O cabeçalho carrega a posição, para
164
- ele saber onde está e quanto falta. O corpo fala do componente dele, e termina numa
165
- pergunta:
166
-
167
- \`\`\`
168
- item 1 de 8
169
-
170
- OverviewHeaderInformation espaça com \`1em\` em 4 lugares, e esse valor não está
171
- ligado ao seu design system.
172
-
173
- styles.module.scss padding: 1em 0
174
- padding-right: 1em
175
- margin-left: 1em
176
- margin-right: 0.5em
177
-
178
- O que você quer fazer?
179
-
180
- 1 deixar como está
181
- 2 usar \`--spacing-md\`, que o seu projeto já tem
182
- ⚠ \`em\` segue a fonte do elemento, então isto pode mudar o espaçamento na tela
183
- 3 criar um nome novo para este valor no seu design system
184
-
185
- [1] [2] [3] ou me diga outra coisa · [parar por aqui]
186
- \`\`\`
187
-
188
- As três saídas são sempre as mesmas, porque são as três que existem de verdade:
189
-
190
- \`\`\`
191
- 1 deixar como está ele segue para o próximo, e o item entra no resumo como não mexido
192
- 2 usar uma variável você faz a edição e confirma em uma linha
193
- SE a troca puder mudar a tela, isso vem escrito NA OPÇÃO, não depois
194
- 3 criar um nome novo vira um pedido, e é ele quem manda - ver o passo 6
195
- \`\`\`
196
-
197
- E \`parar por aqui\` fecha o resumo com o que andou. É sempre legítimo.
198
-
199
- **Quando ele pedir para ver antes**, mostre as linhas e volte a perguntar - isso não conta
200
- como resposta.
201
-
202
- **Na opção 2, liste as variáveis dele quando houver mais de uma candidata.** Ele não decora
203
- o vocabulário do próprio projeto, e escolher entre dois nomes é mais fácil que lembrar de
204
- um.
205
-
206
- ## 5. AS REGRAS, QUE É A METADE QUE NENHUM COMANDO FAZ
207
-
208
- O passo 2 é determinístico e sai de graça. Este não: o sistema carrega uma doutrina em
209
- prosa, e **nada a verifica mecanicamente**. É aqui que você trabalha.
210
-
211
- \`\`\`
212
- MCP system_doctrine
213
- terminal as regras viajam no documento instalado (_synthesisui/ds/<slug>/doctrine.json)
214
- \`\`\`
215
-
216
- Leia as regras e confronte o componente com cada uma. Três respostas possíveis por regra,
217
- e a terceira é a que interessa:
218
-
219
- \`\`\`
220
- cumpre some do relatório. Diga só o total no fim
221
- NÃO cumpre vira um ITEM da fila, com arquivo, linha e as três saídas
222
- a regra não fala sobre isto vira um item de decisão - uma regra nova
223
- \`\`\`
224
-
225
- Uma regra que você teve que interpretar para aplicar não é "cumpre". É o terceiro caso.
226
-
227
- E nunca escreva "3 de 5 cumpridas". Isso lê como boletim, e as 2 que faltam são justamente
228
- as que TÊM conserto pronto - é a melhor notícia do relatório vestida como a pior.
229
-
230
- ## 6. OS PEDIDOS - você mostra o comando, ele roda
231
-
232
- Quando ele escolhe a saída 3, existem três destinos, e todos existem de verdade:
233
-
234
- \`\`\`
235
- um valor sem nome no sistema
236
- -> npx synthesisui request token --value "<valor>" --name "<como se chamaria>" --for "<o caso>"
237
- uma peça que falta
238
- -> npx synthesisui request component --name "<nome>" --for "<o caso>"
239
- o sistema não tem regra sobre este caso
240
- -> npx synthesisui request rule --name "<o que a regra diria>" --for "<o caso que pediu>"
241
- \`\`\`
242
-
243
- **Arquivar em nome dele seria decidir por ele** - e tirar dele a chance de dizer "não, isso
244
- fica local mesmo". Antes de propor uma regra nova, leia a doutrina inteira: uma regra que já
245
- existe e você não achou vira duplicata na fila, e a fila perde valor na terceira.
246
-
247
- E o nome que você propõe para a variável nova segue a convenção DELE. Se o projeto escreve
248
- \`--color-lightgray-700\`, o valor novo não vira \`--ds-color-neutral-4\`.
249
-
250
- ## 6b. A RÉGUA É O QUE DÁ PARA FAZER HOJE, E O TETO SE DIZ JUNTO
251
-
252
- **100% quase nunca é alcançável hoje, e isso não é falha dele.** Se o sistema não tem nome
253
- para \`#555\`, ninguém chega a 100% sem antes decidir criar esse nome. Um medidor que mostra
254
- 0% contra um teto imaginário faz trabalho completo parecer trabalho pela metade.
255
-
256
- Os números para a conta certa já vêm do comando:
257
-
258
- \`\`\`
259
- 3 valores à mão
260
- 2 o projeto já nomeia -> alcançável hoje: 67%
261
- 1 ninguém nomeia -> precisa de uma decisão dele
262
- \`\`\`
263
-
264
- Então o teto de hoje é 67% - e ao falar com ele, isso não se chama "alcançável", se chama
265
- *"o máximo que dá para resolver sem você decidir nada novo"*.
266
-
267
- ## 6b-2. O ÚLTIMO ITEM DA FILA É MANDAR O REGISTRO
268
-
269
- Cada escrita fica gravada numa fila local, e ela **nunca toca a rede** sozinha - essa
270
- divisão é o que mantém a verificação instalada. Uma skill que mandasse por conta própria
271
- seria a verificação ligando para casa.
272
-
273
- Então o envio é um item como os outros, o último, e ele só acontece se ele mandar:
274
-
275
- \`\`\`
276
- item 8 de 8
277
-
278
- você mudou 3 coisas nesta conversa. A plataforma ainda está pontuando este
279
- repositório pelo que ela viu da última vez.
280
-
281
- [mandar agora] [depois] npx synthesisui sync --record-only
282
- \`\`\`
283
-
284
- **\`--record-only\`, e não \`sync\` puro.** O \`sync\` completo re-mede tudo e reescreve as
285
- receitas no RASCUNHO - fazer isso no fim de uma revisão move o chão de quem está revisando.
286
-
287
- O \`sync\` COMPLETO é outro item, e só aparece quando algum item mexeu em FUNDAÇÃO (uma cor,
288
- um degrau, uma variável nova). Aí a plataforma precisa reler, e a skill diz isso sem jargão:
289
-
290
- \`\`\`
291
- isto mexeu numa das bases do seu design system, então a plataforma precisa reler o
292
- seu repositório. Você vê o resultado antes de publicar - ele reescreve as receitas no rascunho.
293
- [rodar agora] [depois] npx synthesisui sync
294
- \`\`\`
295
-
296
- Se nada foi escrito, não ofereça nem um nem outro. Um item que não tem o que mandar é ruído.
297
-
298
- ## 6c. FECHE PELO QUE ANDOU, E MOSTRE O CAMINHO ATÉ 100%
299
-
300
- \`\`\`
301
- OverviewHeaderInformation 2 arquivos
302
-
303
- dá para resolver hoje 82% é o que o seu vocabulário já cobre
304
- resolvido 82% ✓ nada mecânico ficou para trás
305
- deixado como estava 12 os \`em\`, que podem mexer no layout
306
- esperando você 3 valores que ainda não têm nome
307
- \`\`\`
308
-
309
- Nunca "0% -> 0%". Se nada era mecânico, o teto era zero, e a frase é *"não havia nada que
310
- o seu vocabulário de hoje resolvesse - o que existe são N decisões suas"*.
311
-
312
- E quando sobrou pedido, termine com o caminho, porque ele é de dois passos e o primeiro é
313
- dele:
314
-
315
- \`\`\`
316
- para chegar a 100%, faltam dois passos:
317
- 1. autorize o pedido no painel (ele já está na fila)
318
- 2. publique, e rode \`npx synthesisui upgrade\` aqui
319
- depois disso, /sui-adapt neste componente fecha em 100%
320
- \`\`\`
321
-
322
- Autorizar escreve o RASCUNHO, e o repo recebe a última PUBLICADA - por isso os dois passos.
323
- Prometer que \`upgrade\` sozinho traz a variável é mandar a pessoa rodar um comando que
324
- responde "already at the latest version".
325
-
326
- Se a fila esvaziou, o fim é uma linha só:
327
-
328
- \`\`\`
329
- dá para resolver hoje 100%
330
- resolvido 100% ✓ este componente está inteiro no seu design system
331
- \`\`\`
332
-
333
- ## 7. ROTINA
334
-
335
- Ela foi desenhada para duas horas do dia, e a segunda é a que mais rende:
336
-
337
- \`\`\`
338
- semanal "roda o sui-adapt no dashboard" - pega deriva antes de virar hábito
339
- depois de mexer componente novo, ou alteração grande: rode ANTES do commit, enquanto a
340
- decisão ainda está quente e o conserto ainda é barato
341
- \`\`\`
342
-
343
- A verificação automática já roda a metade determinística a cada escrita, calada quando não
344
- há o que dizer. Esta skill é o passo deliberado: ela junta a verificação, a doutrina e a
345
- fila numa conversa só, e termina com o cliente decidindo - não com um relatório.
346
- `;
347
14
  export const ADAPT_SKILL_PATH = ".claude/skills/sui-adapt/SKILL.md";
15
+ export const ADAPT_SKILL = '---\nname: sui-adapt\ndescription: Confronta UM componente (ou uma p\u00e1gina, ou uma pasta) contra o design system instalado e adapta o que der - cobertura de token medida, conserto mec\u00e2nico proposto antes de escrever, e o que sobra classificado em "regra nova" ou "conserto local". Use quando algu\u00e9m aponta para uma pe\u00e7a e pergunta se ela est\u00e1 de acordo com o sistema (ex. "/sui-adapt components/ui/card", "analisa esse componente aqui", "isso aqui segue o meu design system?"), como rotina semanal, ou logo depois de criar/alterar um componente. Mede antes de propor, prop\u00f5e antes de escrever, e nunca arquiva pedido em nome de ningu\u00e9m.\n---\n\n# Adaptar uma pe\u00e7a ao sistema - served live\n\nThis playbook is served from the platform, not shipped in this file - it is\nalways current, and your context only carries the step you are on.\n\n1. Call the `playbook` tool on the `synthesisui` MCP server with\n { "skill": "adapt" } - you get the framing and a table of contents.\n2. Fetch ONLY the chapter for the step you are on:\n { "skill": "adapt", "section": "<id from the toc>" }. Never fetch more\n than the current step needs.\n3. Follow it exactly. When the step is done, fetch the next chapter.\n\nIf the tool answers that you are not signed in, run `npx synthesisui login`\nin the terminal and call it again. If the `synthesisui` MCP server is not\navailable at all, run `npx synthesisui connect`, restart the session, and\ninvoke this skill again.\n';