synthesisui 0.16.443 → 0.16.444

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.
@@ -3656,7 +3656,14 @@ async function askName(suggested) {
3656
3656
  * Never blocks without a person: no TTY means the caller's `--scheme` or the
3657
3657
  * measured default stands.
3658
3658
  */
3659
- async function askScheme(suggested) {
3659
+ async function askScheme(suggested,
3660
+ /** Quem pergunta por outro motivo traz o seu - ver `askFaceFromEvidence`. */
3661
+ cabecalho = {
3662
+ titulo: "Your ladder reaches both ends",
3663
+ linhas: [
3664
+ "Both schemes get built from tokens you already declare. Pick the default.",
3665
+ ],
3666
+ }) {
3660
3667
  if (!process.stdin.isTTY || !process.stdout.isTTY)
3661
3668
  return suggested;
3662
3669
  const options = ["dark", "light"];
@@ -3664,8 +3671,9 @@ async function askScheme(suggested) {
3664
3671
  if (at < 0)
3665
3672
  at = 0;
3666
3673
  console.log("");
3667
- console.log(section("Your ladder reaches both ends"));
3668
- console.log(body(paint.faint("Both schemes get built from tokens you already declare. Pick the default.")));
3674
+ console.log(section(cabecalho.titulo));
3675
+ for (const linha of cabecalho.linhas)
3676
+ console.log(body(paint.faint(linha)));
3669
3677
  console.log("");
3670
3678
  const render = (first) => {
3671
3679
  if (!first)
@@ -3726,6 +3734,48 @@ async function askScheme(suggested) {
3726
3734
  stdin.on("data", onData);
3727
3735
  });
3728
3736
  }
3737
+ /**
3738
+ * A FACE PERGUNTADA COM A EVIDÊNCIA NA FRENTE - decidido por ele em 2026-09-20.
3739
+ *
3740
+ * O QUE ACONTECIA: um projeto que escreve tema em UTILITÁRIO (`dark:bg-…`) e não em token por
3741
+ * escopo caía inteiro em `mustNotGuessPolarity` - a esteira ficava calada, o censo ia sem face, e
3742
+ * o sistema nascia com a face nativa da semente. Medido no `codelevel-monorepo` em 20/09: 255
3743
+ * utilitários dark em 50 arquivos, 0 tokens declarados por escopo, e um sistema `light` no fim.
3744
+ *
3745
+ * POR QUE ISTO NÃO REABRE A PORTA QUE 28/08 FECHOU: aquele portão recusa o palpite por
3746
+ * LUMINÂNCIA - deduzir a face contando quão escuros são os neutros. Aqui não se deduz nada: um
3747
+ * número medido é mostrado e a pessoa responde. Sem TTY, continua sem resposta, que é o ponto.
3748
+ *
3749
+ * E OS DOIS LADOS VÃO JUNTOS. A evidência que abre a pergunta é dark; a página base pode pintar
3750
+ * claro, e isso aparece na mesma tela. Mostrar só a metade que motivou a pergunta seria conduzir
3751
+ * a resposta.
3752
+ */
3753
+ export function faceQuestion(census) {
3754
+ const t = census.signals?.theme;
3755
+ if (!t || t.darkUtilities <= 0)
3756
+ return null;
3757
+ const linhas = [
3758
+ `${t.darkUtilities} dark utilit${t.darkUtilities === 1 ? "y" : "ies"} across ${t.darkFiles} file${t.darkFiles === 1 ? "" : "s"} - a second scheme is real here, and no token of yours says which face opens.`,
3759
+ ];
3760
+ const base = (t.page ?? []).find((p) => p.scheme === "light");
3761
+ if (base)
3762
+ linhas.push(`Your light page paints \`${base.token}\` on ${base.source.replace(/-/g, " ")} - the other half of the same question.`);
3763
+ linhas.push("Nothing is guessed from this. You answer, and we write it down.");
3764
+ return { titulo: "Which face does this project open in", linhas };
3765
+ }
3766
+ /**
3767
+ * E A PERGUNTA SÓ ACONTECE COM ALGUÉM NA FRENTE. Sem TTY não há resposta, e seguir sem ela é o
3768
+ * comportamento que `mustNotGuessPolarity` garante desde 28/08 - o que muda é que agora existe
3769
+ * uma pergunta a fazer quando há alguém para responder.
3770
+ */
3771
+ export async function askFaceFromEvidence(census) {
3772
+ const pergunta = faceQuestion(census);
3773
+ if (!pergunta)
3774
+ return undefined;
3775
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
3776
+ return undefined;
3777
+ return await askScheme("dark", pergunta);
3778
+ }
3729
3779
  /**
3730
3780
  * Which end to offer first when both are available.
3731
3781
  *
@@ -3788,19 +3838,13 @@ export function defaultScheme(declared) {
3788
3838
  }
3789
3839
  /** On a dry run there is nobody to ask, so say what was measured. */
3790
3840
  /**
3791
- * A TELA DO `inspect`, E O ARQUIVO QUE CARREGA O RESTO (dono, 18/09).
3841
+ * O QUE SOBRA DA LEITURA NA TELA, e os dois comandos mostram a MESMA coisa.
3792
3842
  *
3793
- * O QUE O CLIENTE GANHA: ele decide sem rolar. Ficam na tela os quatro blocos que MUDAM o que ele
3794
- * vai fazer em seguida - quanto foi lido, se o root tem mais de um app, o que não resolve, e onde
3795
- * está a leitura inteira. Medido na saída dele em 18/09: 250 linhas, 94 delas a mesma frase sobre
3796
- * nome de parte. Nada se perde: o texto inteiro está no relatório, escrito pela MESMA esteira.
3797
- *
3798
- * A FRASE DO CONTRATO MUDA, E É A ÚNICA COISA QUE MUDA: era "nothing was written, and nothing was
3799
- * sent". Passa a dizer a verdade - nada enviado, um arquivo escrito, e aqui está ele. Uma frase que
3800
- * promete silêncio enquanto o comando escreve um arquivo é pior que o arquivo.
3843
+ * O número que diz o quanto entrou, e as duas seções que mudam o comando seguinte. Elas saem do
3844
+ * transcrito em vez de serem reescritas: a régua que as produziu é a mesma, então a tela nunca
3845
+ * diverge do relatório.
3801
3846
  */
3802
- async function mostrarInspecao(c, root, transcrito) {
3803
- const secoes = secoesDe(transcrito);
3847
+ function mostrarLeitura(c, secoes, titulos = DECIDEM) {
3804
3848
  const valores = c.values?.classes;
3805
3849
  const decisoes = valores ? valores.seen - valores.structure : 0;
3806
3850
  const pct = valores && decisoes > 0
@@ -3808,8 +3852,70 @@ async function mostrarInspecao(c, root, transcrito) {
3808
3852
  : null;
3809
3853
  const lidos = c.coverage?.read ?? 0;
3810
3854
  const total = c.coverage?.components ?? 0;
3855
+ console.log(section("What came out of your files"));
3856
+ console.log(body(`${paint.strong(`${lidos} of ${total}`)} components came out with a blueprint${pct === null
3857
+ ? ""
3858
+ : `, and ${paint.strong(`${pct}%`)} of your ${decisoes} style decisions carry your own vocabulary`}.`));
3859
+ /**
3860
+ * TODAS as ocorrências, e não a primeira: um import com dois escopos declara a organização de
3861
+ * CADA um, e `find` mostraria `packages/ui` e engoliria `packages/charts` - a segunda lacuna
3862
+ * viraria silêncio, que é exatamente o que `INV-COB-08` existe para impedir.
3863
+ */
3864
+ for (const titulo of titulos) {
3865
+ for (const achada of secoes.filter((s) => s.titulo === titulo)) {
3866
+ console.log(section(titulo));
3867
+ for (const linha of achada.linhas)
3868
+ console.log(linha);
3869
+ }
3870
+ }
3871
+ }
3872
+ /**
3873
+ * O RELATÓRIO DO `import`, ESCRITO SEM PERGUNTAR - e a diferença com o `inspect` é de contrato.
3874
+ *
3875
+ * `INV-INSPECT-01` promete que o `inspect` não toca no repositório, e por isso lá o arquivo só
3876
+ * nasce depois de um sim. O `import` já escreveu o censo, o `not-expressed.md`, o `.gitignore` e
3877
+ * a pasta `ds/` antes de chegar aqui: parar para perguntar por um HTML seria cerimônia, não
3878
+ * proteção. Quem quer olhar antes de escrever tem `--dry` e `inspect`.
3879
+ */
3880
+ async function escreverRelatorio(c, root, secoes) {
3881
+ if (secoes.length === 0)
3882
+ return;
3811
3883
  const arquivo = join(root, "_synthesisui", "report.html");
3812
- const html = () => relatorioHtml({
3884
+ try {
3885
+ await mkdir(dirname(arquivo), { recursive: true });
3886
+ await writeGovernanceIgnore(root).catch(() => null);
3887
+ await writeFile(arquivo, relatorioDe(c, root, secoes), "utf8");
3888
+ }
3889
+ catch (error) {
3890
+ /* O relatório é o resto da leitura, não o trabalho: um disco cheio no fim de um import que
3891
+ deu certo não pode terminar como falha. Diz o que não deu e devolve o terminal. */
3892
+ console.log("");
3893
+ console.log(body(`The full report was not written: ${error instanceof Error ? error.message : String(error)}`));
3894
+ return;
3895
+ }
3896
+ console.log("");
3897
+ console.log(section("The whole reading, in one file"));
3898
+ console.log(body(`${secoes.length} sections did not print here - every value, every fragment, every part.`));
3899
+ console.log(body(` ${paint.strong(relative(root, arquivo))}`));
3900
+ console.log(body(` ${paint.dim(`file://${arquivo}`)}`));
3901
+ console.log("");
3902
+ }
3903
+ /**
3904
+ * O RELATÓRIO, MONTADO NUM LUGAR SÓ E LIDO POR DOIS COMANDOS.
3905
+ *
3906
+ * O `inspect` e o `import` cortam a mesma tela e guardam o mesmo resto. Duas montagens do mesmo
3907
+ * documento divergiriam no dia em que uma fosse editada - e a esteira de captura existe
3908
+ * exatamente para que cada medição tenha UMA redação.
3909
+ */
3910
+ function relatorioDe(c, root, secoes) {
3911
+ const valores = c.values?.classes;
3912
+ const decisoes = valores ? valores.seen - valores.structure : 0;
3913
+ const pct = valores && decisoes > 0
3914
+ ? Math.round((valores.interpreted / decisoes) * 100)
3915
+ : null;
3916
+ const lidos = c.coverage?.read ?? 0;
3917
+ const total = c.coverage?.components ?? 0;
3918
+ return relatorioHtml({
3813
3919
  cabecalho: {
3814
3920
  repo: c.measured?.repo ?? basename(root),
3815
3921
  medidoEm: c.measured?.at ?? "",
@@ -3864,22 +3970,24 @@ async function mostrarInspecao(c, root, transcrito) {
3864
3970
  muda: oQueMuda(c),
3865
3971
  passos: passos(),
3866
3972
  });
3867
- console.log(section("What came out of your files"));
3868
- console.log(body(`${paint.strong(`${lidos} of ${total}`)} components came out with a blueprint${pct === null
3869
- ? ""
3870
- : `, and ${paint.strong(`${pct}%`)} of your ${decisoes} style decisions carry your own vocabulary`}.`));
3871
- /**
3872
- * OS DOIS BLOCOS QUE MUDAM O QUE ELE FAZ EM SEGUIDA, e eles saem do transcrito em vez de serem
3873
- * reescritos: a régua que os produziu é a mesma, então a tela nunca diverge do relatório.
3874
- */
3875
- for (const titulo of DECIDEM) {
3876
- const achada = secoes.find((s) => s.titulo === titulo);
3877
- if (!achada)
3878
- continue;
3879
- console.log(section(titulo));
3880
- for (const linha of achada.linhas)
3881
- console.log(linha);
3882
- }
3973
+ }
3974
+ /**
3975
+ * A TELA DO `inspect`, E O ARQUIVO QUE CARREGA O RESTO (dono, 18/09).
3976
+ *
3977
+ * O QUE O CLIENTE GANHA: ele decide sem rolar. Ficam na tela os quatro blocos que MUDAM o que ele
3978
+ * vai fazer em seguida - quanto foi lido, se o root tem mais de um app, o que não resolve, e onde
3979
+ * está a leitura inteira. Medido na saída dele em 18/09: 250 linhas, 94 delas a mesma frase sobre
3980
+ * nome de parte. Nada se perde: o texto inteiro está no relatório, escrito pela MESMA esteira.
3981
+ *
3982
+ * A FRASE DO CONTRATO MUDA, E É A ÚNICA COISA QUE MUDA: era "nothing was written, and nothing was
3983
+ * sent". Passa a dizer a verdade - nada enviado, um arquivo escrito, e aqui está ele. Uma frase que
3984
+ * promete silêncio enquanto o comando escreve um arquivo é pior que o arquivo.
3985
+ */
3986
+ async function mostrarInspecao(c, root, transcrito) {
3987
+ const secoes = secoesDe(transcrito);
3988
+ const arquivo = join(root, "_synthesisui", "report.html");
3989
+ const html = () => relatorioDe(c, root, secoes);
3990
+ mostrarLeitura(c, secoes);
3883
3991
  /**
3884
3992
  * O ARQUIVO SÓ NASCE SE ELE PEDIR (dono, 18/09) - e é isto que mantém `INV-INSPECT-01` de pé.
3885
3993
  *
@@ -3955,6 +4063,18 @@ const DECIDEM = [
3955
4063
  "This root holds several projects",
3956
4064
  "These resolve to nothing",
3957
4065
  ];
4066
+ /**
4067
+ * E O `import` CARREGA UMA A MAIS: como o projeto está organizado.
4068
+ *
4069
+ * `INV-COB-08` promete essa seção no terminal de quem importa - é ali que uma organização que
4070
+ * esta versão não sabe nomear se declara, com o número medido, em vez de sumir. Ela cabe porque
4071
+ * em 20/09 as 120 linhas de nome de parte saíram de dentro dela e ganharam seção própria; sem
4072
+ * essa separação, honrar o invariante custaria o despejo inteiro de volta.
4073
+ */
4074
+ const NA_TELA_DO_IMPORT = [
4075
+ "How this project is organised",
4076
+ ...DECIDEM,
4077
+ ];
3958
4078
  function sayReach(c) {
3959
4079
  const reach = ladderReach(c.declared);
3960
4080
  console.log("");
@@ -4424,8 +4544,17 @@ quiet = false) {
4424
4544
  }
4425
4545
  const authored = Object.keys(read).length;
4426
4546
  const total = authored || Object.keys(census.looks ?? {}).length;
4547
+ /**
4548
+ * A ANATOMIA GANHA CABEÇALHO PRÓPRIO - e antes de 20/09 ela não tinha nenhum.
4549
+ *
4550
+ * Sem régua, estas linhas pertenciam à última seção aberta, que é "How this project is
4551
+ * organised". Na saída medida do `codelevel-monorepo` isso deu uma seção de 124 linhas em que
4552
+ * as quatro primeiras falavam de organização e as outras 120 falavam de nome de parte. Quem
4553
+ * lia não estava lendo uma seção longa: estava lendo duas coladas por acidente de parser.
4554
+ */
4427
4555
  if (named > 0 || shaped > 0) {
4428
4556
  say("");
4557
+ say(section("What your components are made of"));
4429
4558
  say(body(
4430
4559
  // "Your reading" only when there WAS one - otherwise the names came off
4431
4560
  // their own markup, and claiming otherwise credits somebody's absent work.
@@ -4444,8 +4573,17 @@ quiet = false) {
4444
4573
  say(body(`${libs.size} librar${libs.size === 1 ? "y" : "ies"} your components need (${named_}) - ${libs.size === 1 ? "it does" : "they do"} not render here, and what to know about ${libs.size === 1 ? "it" : "them"} travels as rules`));
4445
4574
  }
4446
4575
  // NO SILENT FIX. A correction made quietly is a lie about what they wrote.
4447
- for (const n of notes)
4448
- say(body(n));
4576
+ /**
4577
+ * E ELAS SÃO UMA SEÇÃO, não um rodapé da anterior: são 94 linhas na saída medida dele, uma
4578
+ * por parte cujo nome colidiu. O fato de cada uma é verdadeiro e nenhuma muda o passo
4579
+ * seguinte - é exatamente o material do relatório.
4580
+ */
4581
+ if (notes.size > 0) {
4582
+ say("");
4583
+ say(section("Names your own classes produced"));
4584
+ for (const n of notes)
4585
+ say(body(n));
4586
+ }
4449
4587
  }
4450
4588
  export async function runImport(opts) {
4451
4589
  const root = opts.root ?? process.cwd();
@@ -4568,15 +4706,20 @@ export async function runImport(opts) {
4568
4706
  phase(1, 3, "Measuring the repository");
4569
4707
  console.log(section("Reading your project"));
4570
4708
  /**
4571
- * A PARTIR DAQUI O `inspect` GUARDA EM VEZ DE IMPRIMIR - ver `transcript.ts`.
4709
+ * A PARTIR DAQUI OS DOIS GUARDAM EM VEZ DE IMPRIMIR - ver `transcript.ts`.
4572
4710
  *
4573
4711
  * As dezessete seções continuam sendo escritas exatamente como sempre foram; o que muda é o
4574
- * destino. Elas vão para o relatório, e o terminal fica com os quatro blocos que uma pessoa
4575
- * usa para DECIDIR. Medido na saída do `codelevel-monorepo` em 18/09: 250 linhas, das quais 94
4576
- * eram a mesma frase sobre nome de parte - 40% do relatório num detalhe sem ação possível.
4712
+ * destino. Elas vão para o relatório, e o terminal fica com os blocos que uma pessoa usa para
4713
+ * DECIDIR. Medido na saída do `codelevel-monorepo` em 18/09: 250 linhas, das quais 94 eram a
4714
+ * mesma frase sobre nome de parte - 40% do relatório num detalhe sem ação possível.
4715
+ *
4716
+ * O `import` ENTROU AQUI EM 20/09, e a régua é dele: *"a mesma tela do `inspect`"*. O corte
4717
+ * existia desde 18/09 e valia só para o comando que NÃO escreve - então quem importava de
4718
+ * verdade, que é quem mais precisa entender o que aconteceu, continuava recebendo o despejo.
4719
+ * Medido na saída dele naquele dia: 485 linhas, 42.645 caracteres, 23 seções, e três frases
4720
+ * ocupando 116 das 393 linhas com conteúdo.
4577
4721
  */
4578
- if (opts.inspect)
4579
- ligarCaptura();
4722
+ ligarCaptura();
4580
4723
  /**
4581
4724
  * UMA MEDIÇÃO POR ESCOPO, E DEPOIS A FUSÃO.
4582
4725
  *
@@ -4697,6 +4840,15 @@ export async function runImport(opts) {
4697
4840
  await mostrarInspecao(census, root, transcrito);
4698
4841
  return;
4699
4842
  }
4843
+ /**
4844
+ * O QUE O `import` GUARDOU, e ele segue - a leitura acabou, o envio não começou.
4845
+ *
4846
+ * O `inspect` termina aqui porque a leitura ERA o comando. O `import` continua, então a tela
4847
+ * ganha o mesmo resumo e o transcrito espera: o relatório só é escrito no fim, quando as
4848
+ * seções do sistema criado já tiverem entrado nele.
4849
+ */
4850
+ const relatorio = secoesDe(desligarCaptura());
4851
+ mostrarLeitura(census, relatorio, NA_TELA_DO_IMPORT);
4700
4852
  const out = opts.census ?? join(root, "_synthesisui", "census.json");
4701
4853
  await mkdir(dirname(out), { recursive: true });
4702
4854
  await writeFile(out, `${JSON.stringify(census, null, 2)}\n`, "utf8");
@@ -4725,6 +4877,9 @@ export async function runImport(opts) {
4725
4877
  /* `printComponents` subiu para antes de `summarize` e vale para todo caminho - repetir
4726
4878
  aqui imprimiria os componentes duas vezes na mesma execução. */
4727
4879
  printAgentContract();
4880
+ /* O `--dry` já escreveu o censo nesta pasta; guardar a leitura ao lado dele é o mesmo gesto,
4881
+ e sem isso quem pede para olhar antes de enviar sai sem a tela E sem o arquivo. */
4882
+ await escreverRelatorio(census, root, relatorio);
4728
4883
  console.log("");
4729
4884
  return;
4730
4885
  }
@@ -4746,7 +4901,7 @@ export async function runImport(opts) {
4746
4901
  ? read.has[0]
4747
4902
  : (observedPolarity(census) ??
4748
4903
  (mustNotGuessPolarity(census)
4749
- ? undefined
4904
+ ? await askFaceFromEvidence(census)
4750
4905
  : reach.light && reach.dark
4751
4906
  ? await askScheme(defaultScheme(census.declared))
4752
4907
  : reach.dark
@@ -4908,8 +5063,18 @@ export async function runImport(opts) {
4908
5063
  */
4909
5064
  if (payload?.group)
4910
5065
  console.log(body(paint.dim(`In the group ${payload.group}.`)));
4911
- for (const note of payload?.notes ?? [])
4912
- console.log(body(paint.dim(note)));
5066
+ /**
5067
+ * AS NOTAS DO QUE FOI MAPEADO VÃO PARA O RELATÓRIO (dono, 20/09).
5068
+ *
5069
+ * São 57 linhas na saída medida dele - cada token que chegou, cada um que esperou, cada papel
5070
+ * que ficou com o nome dele. Nada disso muda o comando seguinte, e todas juntas empurram para
5071
+ * fora da tela as três linhas que mudam: onde o sistema está, o que falta instalar e a URL.
5072
+ */
5073
+ if ((payload?.notes ?? []).length > 0)
5074
+ relatorio.push({
5075
+ titulo: "What your system carries",
5076
+ linhas: (payload?.notes ?? []).map((note) => ` ${note}`),
5077
+ });
4913
5078
  /**
4914
5079
  * A NOTA COM QUE O SISTEMA DELE NASCEU, e as três lentes - ver `baseline-craft.ts`.
4915
5080
  *
@@ -4934,8 +5099,13 @@ export async function runImport(opts) {
4934
5099
  console.log("");
4935
5100
  console.log(section("What a v2 would normalize"));
4936
5101
  console.log(body(`${paint.strong(String(n.decisions))} colour decision${n.decisions === 1 ? "" : "s"} to settle - ${paint.strong(String(n.governs ?? n.retires ?? 0))} uses would come under a name.`));
4937
- for (const line of n.lines ?? [])
4938
- console.log(body(paint.dim(line)));
5102
+ /* O NÚMERO fica na tela e a lista vai para o arquivo: a decisão é aprovar ou não, e ela se
5103
+ toma com "27 decisões, 112 usos". Qual hex absorve qual é a leitura de quem já disse sim. */
5104
+ if ((n.lines ?? []).length > 0)
5105
+ relatorio.push({
5106
+ titulo: "What a v2 would normalize",
5107
+ linhas: (n.lines ?? []).map((line) => ` ${line}`),
5108
+ });
4939
5109
  console.log("");
4940
5110
  console.log(body("Nothing was changed. You approve it there:"));
4941
5111
  }
@@ -5016,4 +5186,11 @@ export async function runImport(opts) {
5016
5186
  console.log("");
5017
5187
  }
5018
5188
  }
5189
+ /**
5190
+ * E O RESTO DA LEITURA, POR ÚLTIMO - depois de tudo que ele PRECISA fazer.
5191
+ *
5192
+ * O caminho do arquivo é a última linha de propósito: quem vai agir já leu o que agir, e quem
5193
+ * quer entender o que a medição encontrou sai daqui com um endereço em vez de um scroll.
5194
+ */
5195
+ await escreverRelatorio(census, root, relatorio);
5019
5196
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.443",
3
+ "version": "0.16.444",
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": {