synthesisui 0.16.358 → 0.16.360

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.
@@ -1,7 +1,12 @@
1
1
  import { access, mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  const HOOK_MATCHER = "Write|Edit|MultiEdit";
4
- const exists = (p) => access(p).then(() => true, () => false);
4
+ /**
5
+ * Exportado porque a REGRA é uma só: a pasta de uma ferramenta é a evidência de que ela é usada, e
6
+ * a mesma evidência decide o MCP daqui e a casa do bloco em `claude-md.ts`. Duas cópias deste
7
+ * predicado seriam duas chances de as duas decisões divergirem.
8
+ */
9
+ export const exists = (p) => access(p).then(() => true, () => false);
5
10
  async function readJson(path) {
6
11
  const raw = await readFile(path, "utf8").catch(() => "");
7
12
  if (!raw.trim())
package/dist/claude-md.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
- import { hasHook } from "./agent-wiring.js";
3
+ import { exists, hasHook } from "./agent-wiring.js";
4
4
  import { declaredReference } from "./group-role.js";
5
5
  import { recallAvailable } from "./memory/availability.js";
6
6
  const START = "<!-- synthesisui:start -->";
@@ -573,10 +573,11 @@ _Block managed by the CLI - do not edit by hand; run \`synthesisui ${onlyAdopted
573
573
  */
574
574
  const HOMES = [
575
575
  { path: "CLAUDE.md" },
576
- { path: "AGENTS.md" },
576
+ { path: "AGENTS.md", bornWhen: ".codex" },
577
577
  {
578
578
  path: ".cursor/rules/synthesisui.mdc",
579
579
  frontmatter: "---\ndescription: Design system rules - read before writing UI\nalwaysApply: true\n---\n\n",
580
+ bornWhen: ".cursor",
580
581
  },
581
582
  ];
582
583
  export async function syncClaudeMd(projectRoot) {
@@ -594,15 +595,18 @@ export async function syncClaudeMd(projectRoot) {
594
595
  existing = null;
595
596
  }
596
597
  /**
597
- * O ARQUIVO NOVO SÓ NASCE PARA O CLAUDE.md.
598
+ * O ARQUIVO NOVO NASCE QUANDO O REPOSITÓRIO JÁ MOSTROU QUEM ELE USA - ver `bornWhen`.
598
599
  *
599
- * Criar `AGENTS.md` num repo que nunca teve um é decidir por outra pessoa qual agente ela usa, e
600
- * um arquivo que apareceu sozinho na raiz é a primeira coisa que alguém apaga com raiva. Nos
601
- * outros dois a regra é: se o arquivo existe, o bloco entra e se mantém atualizado; se não
602
- * existe, não inventamos um.
600
+ * `CLAUDE.md` sem condição, porque é a casa que a gente cria num repo que não tem nenhuma. Os
601
+ * outros dois só quando a pasta da ferramenta está lá: criar `AGENTS.md` num repo que só usa
602
+ * Claude Code continua sendo decidir por outra pessoa qual agente ela usa, e um arquivo que
603
+ * apareceu sozinho na raiz é a primeira coisa que alguém apaga com raiva.
603
604
  */
604
605
  if (existing === null) {
605
- if (home.path !== "CLAUDE.md")
606
+ const authorised = home.path === "CLAUDE.md" ||
607
+ (home.bornWhen != null &&
608
+ (await exists(join(projectRoot, home.bornWhen))));
609
+ if (!authorised)
606
610
  continue;
607
611
  await mkdir(dirname(path), { recursive: true }).catch(() => { });
608
612
  await writeFile(path, `${home.frontmatter ?? ""}${region}\n`, "utf8");
@@ -636,6 +640,37 @@ export async function syncClaudeMd(projectRoot) {
636
640
  * Um repo com `AGENTS.md` e `.cursor/` ganhou governança nos três lugares e não tinha como saber -
637
641
  * e o que não se diz não é adotado.
638
642
  */
643
+ /**
644
+ * O COMANDO QUE ABRE CADA AGENTE - derivado de qual casa do bloco existe, nunca de uma lista fixa.
645
+ *
646
+ * O QUE O CLIENTE GANHA: o fim de um comando mostra o comando literal que abre o agente DELE, então
647
+ * "abra uma sessão nova" deixa de ser uma instrução que ele precisa traduzir (dono, 03/09: "seria
648
+ * legal ter os comandos para abrir, apenas para ficar fácil").
649
+ *
650
+ * MORA AO LADO DE `HOMES` de propósito. A mesma tabela que sabe onde o bloco vive é a que sabe como
651
+ * aquele agente abre - um agente novo entra em UM lugar. Se as duas listas morassem separadas, a
652
+ * segunda envelheceria calada no dia em que a primeira crescesse.
653
+ *
654
+ * O CURSOR NÃO TEM LINHA AQUI, e a ausência é a resposta certa: ele é um editor que se abre, não um
655
+ * comando de terminal. Inventar um `cursor .` mandaria alguém rodar o que talvez não exista no PATH
656
+ * dele.
657
+ *
658
+ * AS FLAGS SÃO AS QUE DISPENSAM APROVAR CADA ESCRITA, e o import escreve muito. Elas são DITAS na
659
+ * saída - o que a flag dispensa aparece em uma linha ao lado -, porque um comando cujo próprio nome
660
+ * carrega "dangerously" impresso sem uma palavra seria a gente escondendo o que ele faz.
661
+ */
662
+ const OPENS = {
663
+ "CLAUDE.md": "claude --dangerously-skip-permissions",
664
+ "AGENTS.md": "codex --yolo",
665
+ };
666
+ /**
667
+ * Os comandos que abrem os agentes que ESTE repositório mostra ter - vazio quando nenhum deles
668
+ * carrega o bloco, e aí a saída não fala de abrir nada.
669
+ */
670
+ export async function agentOpenCommands(projectRoot) {
671
+ const homes = await blockHomes(projectRoot);
672
+ return homes.map((h) => OPENS[h]).filter((c) => Boolean(c));
673
+ }
639
674
  export async function blockHomes(projectRoot) {
640
675
  const found = [];
641
676
  for (const home of HOMES) {
@@ -2,6 +2,7 @@ import { readdir, readFile } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { join, resolve } from "node:path";
4
4
  import { pinnedHookVersion } from "../agent-wiring.js";
5
+ import { agentOpenCommands } from "../claude-md.js";
5
6
  import { isOlderCli } from "../cli-version.js";
6
7
  import { readToken, resolveRegistry } from "../config.js";
7
8
  import { unsentEvents } from "../doctor/ledger.js";
@@ -633,7 +634,8 @@ export async function nextStepFor(root) {
633
634
  }
634
635
  : {
635
636
  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
+ headline: "Open a new agent session in this repo",
638
+ open: await agentOpenCommands(root).catch(() => []),
637
639
  say: "import my design system",
638
640
  why: "I read what is already in your code - the colours, the type, the shapes, the components. Nothing is invented.",
639
641
  };
@@ -646,12 +648,36 @@ export async function nextStepFor(root) {
646
648
  * traz as linhas em branco de graça, que é o "respiro" pedido.
647
649
  */
648
650
  export function renderNextStep(next, freshSession) {
649
- const lines = [section("Do this next"), body(next.headline), ""];
650
- if (next.say)
651
+ const lines = [section("Do this next")];
652
+ /**
653
+ * NUMERA SÓ QUANDO SÃO DOIS PASSOS. Abrir o agente e falar com ele são duas coisas, e sem o "1" e
654
+ * o "2" a pessoa lê dois blocos azuis e não sabe se escolhe um ou faz os dois. Com um passo só, o
655
+ * número seria decoração.
656
+ */
657
+ const numbered = Boolean(next.open?.length && next.say);
658
+ if (next.open?.length) {
659
+ lines.push(body(numbered ? `1 ${next.headline}` : next.headline), "");
660
+ lines.push(paint.blue(snippet(next.open)), "");
661
+ }
662
+ if (next.say) {
663
+ if (next.open?.length)
664
+ lines.push(body(numbered ? "2 and say" : "and say"), "");
665
+ else
666
+ lines.push(body(`${next.headline} and say:`), "");
651
667
  lines.push(paint.blue(snippet([next.say])), "");
652
- if (next.run?.length)
668
+ }
669
+ if (next.run?.length) {
670
+ lines.push(body(next.headline), "");
653
671
  lines.push(paint.blue(snippet(next.run)), "");
672
+ }
654
673
  lines.push(...bodyWrapped(next.why).map(paint.dim));
674
+ /** O que a flag dispensa, dito - ver `OPENS` em `claude-md.ts`. E concordando: um repo com um
675
+ * agente só recebe uma linha no singular, porque "those flags" sobre um comando lê como se a
676
+ * pessoa tivesse perdido uma opção da tela. */
677
+ if (next.open?.length)
678
+ lines.push(...bodyWrapped(next.open.length === 1
679
+ ? "That flag lets it write without asking each time. Drop it to approve every change yourself."
680
+ : "Those flags let it write without asking each time. Drop them to approve every change yourself.").map(paint.dim));
655
681
  if (freshSession)
656
682
  lines.push(...bodyWrapped(freshSession.mcp
657
683
  ? "A new session is what loads the skills and tools just installed, and the project's tools ask for approval once - say yes."
@@ -152,7 +152,17 @@
152
152
  * comentou vinha sendo escrito na pasta dele como componente de verdade. Medido em 14 arquivos
153
153
  * das duas populações o nó fantasma era a RAIZ - o elemento cujas classes viram a `base`.
154
154
  */
155
- export const MATERIALISER_SINCE = "0.16.349";
155
+ /**
156
+ * 0.16.349 -> 0.16.360 em 03/09: a casa do bloco de governança passa a NASCER da pasta da
157
+ * ferramenta. Um repositório com `.codex/` recebe `AGENTS.md`, e um com `.cursor/` recebe
158
+ * `.cursor/rules/synthesisui.mdc` - dois arquivos que o materializador antes só atualizava se a
159
+ * pessoa já os tivesse escrito à mão. São bytes NOVOS na pasta dele, então esta é a primeira vez em
160
+ * seis cobranças que o passo 1 dá SIM.
161
+ *
162
+ * O que o cliente ganha ao rodar `upgrade`: as regras do sistema passam a existir no arquivo que o
163
+ * agente DELE lê, em vez de só no do Claude Code.
164
+ */
165
+ export const MATERIALISER_SINCE = "0.16.360";
156
166
  /**
157
167
  * A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
158
168
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.358",
3
+ "version": "0.16.360",
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": {