synthesisui 0.16.157 → 0.16.158

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.
@@ -79,14 +79,42 @@ async function wireHook(root, command) {
79
79
  const settings = await readJson(path);
80
80
  const hooks = (settings.hooks ?? {});
81
81
  const post = Array.isArray(hooks.PostToolUse) ? hooks.PostToolUse : [];
82
- // Idempotent on the COMMAND, not on the whole entry: someone may have
83
- // widened the matcher or added a second hook beside ours, and running this
84
- // again must not undo that.
82
+ /**
83
+ * O CASO QUE ESTA FUNÇÃO NÃO COBRIA, e ele tornava o `connect` uma promessa falsa.
84
+ *
85
+ * Ela achava QUALQUER hook contendo "synthesisui" e devolvia "already there" sem olhar a versão -
86
+ * então um hook pinado nunca subia. Medido em 06/08: o dono rodou `npx synthesisui connect` com o
87
+ * 0.16.157 e a saída disse `· .claude/settings.json already had it / npx synthesisui@0.16.153 hook`.
88
+ * O pin era permanente até alguém editar o arquivo à mão, e a mensagem que a gente acabou de escrever
89
+ * - "rode connect, depois reinicie" - mandava rodar um comando que não fazia a coisa.
90
+ *
91
+ * Agora o comando é REESCRITO no lugar quando ele é nosso e difere. O que a idempotência original
92
+ * protegia continua protegido: o matcher fica como estiver, os hooks vizinhos ficam, e uma segunda
93
+ * entrada nunca é criada - duas entradas rodariam o verificador duas vezes por escrita.
94
+ */
85
95
  const mine = post
86
96
  .flatMap((e) => e.hooks ?? [])
87
97
  .find((h) => (h.command ?? "").includes("synthesisui"));
88
- if (mine)
89
- return { status: "already there", command: mine.command ?? command };
98
+ if (mine) {
99
+ const was = mine.command ?? "";
100
+ /**
101
+ * SÓ UM PIN NOSSO É SUBSTITUÍDO, e a fronteira é estreita de propósito.
102
+ *
103
+ * Um comando SEM versão (`npx synthesisui hook`) ou com instalação local é escolha deliberada de
104
+ * alguém: reescrevê-lo seria pinar a decisão da pessoa, e um spec já protegia isso - o caso do
105
+ * matcher alargado à mão. O que sobe é `synthesisui@<semver>` para outro semver, que é o único caso
106
+ * em que "already there" mentia (dono, 06/08: `connect` do 0.16.157 imprimindo
107
+ * `npx synthesisui@0.16.153 hook` como "already had it").
108
+ */
109
+ const pinnedAt = /synthesisui@(\d+\.\d+\.\d+)/.exec(was)?.[1];
110
+ const proposedAt = /synthesisui@(\d+\.\d+\.\d+)/.exec(command)?.[1];
111
+ if (!pinnedAt || !proposedAt || pinnedAt === proposedAt)
112
+ return { status: "already there", command: was || command };
113
+ mine.command = command;
114
+ await mkdir(dir, { recursive: true });
115
+ await writeFile(path, `${JSON.stringify({ ...settings, hooks: { ...hooks, PostToolUse: post } }, null, 2)}\n`, "utf8");
116
+ return { status: "updated", command, was };
117
+ }
90
118
  post.push({
91
119
  matcher: HOOK_MATCHER,
92
120
  hooks: [{ type: "command", command }],
@@ -111,8 +139,20 @@ async function wireMcpAt(root, version, rel) {
111
139
  const path = join(root, rel);
112
140
  const config = await readJson(path);
113
141
  const servers = (config.mcpServers ?? {});
114
- if (servers.synthesisui)
142
+ /**
143
+ * E O MESMO DEFEITO AQUI. Uma entrada nossa que já existe era aceita sem olhar o CONTEÚDO, então quem
144
+ * conectou antes de 06/08 - quando o MCP passou a flutuar em `@latest` - ficou com um servidor pinado
145
+ * para sempre, e nenhum `connect` o soltava. Só a NOSSA entrada é reescrita; o resto do arquivo é
146
+ * dele.
147
+ */
148
+ const current = servers.synthesisui;
149
+ const pinned = current &&
150
+ Array.isArray(current.args) &&
151
+ current.args.some((a) => /^synthesisui@\d/.test(String(a)));
152
+ if (current && !pinned)
115
153
  return "already there";
154
+ /** Reescrita de uma entrada PINADA: é atualização, e a saída tem que dizer isso. */
155
+ const replacing = Boolean(current && pinned);
116
156
  const local = await exists(join(root, "node_modules", ".bin", "synthesisui"));
117
157
  await mkdir(dirname(path), { recursive: true }).catch(() => { });
118
158
  await writeFile(path, `${JSON.stringify({
@@ -142,7 +182,7 @@ async function wireMcpAt(root, version, rel) {
142
182
  },
143
183
  },
144
184
  }, null, 2)}\n`, "utf8");
145
- return "added";
185
+ return replacing ? "updated" : "added";
146
186
  }
147
187
  /**
148
188
  * Os caminhos de MCP que este repo merece: sempre o do Claude Code, e o do Cursor quando `.cursor/`
@@ -165,11 +205,12 @@ export async function wireAgent(root, version, want) {
165
205
  const proposed = await hookCommand(root, version);
166
206
  const hook = want.hook
167
207
  ? await wireHook(root, proposed)
168
- : { status: "skipped", command: proposed };
208
+ : { status: "skipped", command: proposed, was: undefined };
169
209
  return {
170
210
  // What is actually installed - the only thing worth printing.
171
211
  command: hook.command,
172
212
  hook: hook.status,
213
+ ...(hook.was ? { was: hook.was } : {}),
173
214
  mcp: want.mcp ? await wireMcp(root, version) : "skipped",
174
215
  };
175
216
  }
@@ -41,7 +41,14 @@ export async function connect(opts) {
41
41
  if (want.hook) {
42
42
  console.log(body(wired.hook === "added"
43
43
  ? "✓ .claude/settings.json the check now runs after every write"
44
- : "· .claude/settings.json already had it"));
44
+ : wired.hook === "updated"
45
+ ? /**
46
+ * A LINHA QUE FALTAVA, e a ausência dela fazia o comando mentir: rodando o 0.16.157, a
47
+ * saída dizia "already had it" e imprimia `npx synthesisui@0.16.153 hook` embaixo
48
+ * (dono, 06/08). Quem lê "already had it" fecha o terminal.
49
+ */
50
+ `✓ .claude/settings.json the check moved from ${wired.was?.match(/synthesisui@(\d+\.\d+\.\d+)/)?.[1] ?? "an older version"} to this one`
51
+ : "· .claude/settings.json already had it"));
45
52
  console.log(snippet([wired.command]));
46
53
  }
47
54
  /**
@@ -55,7 +62,11 @@ export async function connect(opts) {
55
62
  for (const one of wired.mcp)
56
63
  console.log(body(one.status === "added"
57
64
  ? `✓ ${one.path.padEnd(22)} ${MCP_TOOL_COUNT} tools, so the agent can ask instead of guess`
58
- : `· ${one.path.padEnd(22)} already had it`));
65
+ : one.status === "updated"
66
+ ? /** Uma entrada pinada antes de o MCP flutuar ficava pinada para sempre - nenhum
67
+ * `connect` a soltava, e o agente segurava um leitor velho sem saber. */
68
+ `✓ ${one.path.padEnd(22)} unpinned - it now follows the published reader`
69
+ : `· ${one.path.padEnd(22)} already had it`));
59
70
  }
60
71
  /**
61
72
  * ONDE O BLOCO CAIU, DITO POR NOME.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.157",
3
+ "version": "0.16.158",
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": {