synthesisui 0.16.140 → 0.16.141

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.
@@ -2254,7 +2254,7 @@ function rootBehaviour(tag, sketch) {
2254
2254
  ? { rootProps: first.behaviourProps }
2255
2255
  : {};
2256
2256
  }
2257
- async function resolveReadParts(census, root) {
2257
+ export async function resolveReadParts(census, root) {
2258
2258
  /**
2259
2259
  * A READING IS OPTIONAL HERE. THE SKETCH IS NOT.
2260
2260
  *
@@ -3,8 +3,9 @@ import { join, resolve } from "node:path";
3
3
  import { readToken, resolveRegistry } from "../config.js";
4
4
  import { readEvents } from "../doctor/ledger.js";
5
5
  import { checkableName, closeRequest, readRequests, verifyAndCloseRequests, } from "../doctor/requests.js";
6
- import { body, section } from "../output.js";
6
+ import { body, paint, section } from "../output.js";
7
7
  import { repoStateOf } from "../repo-state.js";
8
+ import { resolveReadParts, takeCensus } from "./import.js";
8
9
  /**
9
10
  * What each decision means ON THIS MACHINE - the card decides, the sync
10
11
  * enacts. The web never touches the local file; this line is printed right
@@ -83,6 +84,19 @@ export async function sync(opts) {
83
84
  console.log(body("No installed system here, so there is nowhere to sync to."));
84
85
  return;
85
86
  }
87
+ /**
88
+ * A MEDIÇÃO NOVA, ANTES DO REGISTRO - e é isto que faz um conserto de leitura alcançar alguém.
89
+ *
90
+ * Um leitor novo do CLI precisa OLHAR o repo: o que a plataforma guarda é o censo, e o fonte fica
91
+ * aqui por decisão de privacidade. Essa passada é irredutível. O que era redutível é a cerimônia -
92
+ * até hoje "re-importar" significava a skill inteira, seis perguntas, e um agente reescrevendo 261
93
+ * anatomias que já estavam gravadas (dono, 05/08).
94
+ *
95
+ * Então: a plataforma devolve o que já foi decidido (o escopo e a leitura), o CLI mede com o leitor
96
+ * ATUAL, funde a leitura no censo fresco, AVISA o que seria reescrito, e envia. Sem entrevista.
97
+ */
98
+ if (!opts.recordOnly)
99
+ await remeasure({ root, base, token, slug, cli: opts.cli });
86
100
  // Verification first, so the snapshot never reports "open" about something
87
101
  // the installed css already delivers - a request closes because a machine
88
102
  // CHECKED, not because someone remembered to say so (dono, 29/07).
@@ -152,3 +166,133 @@ export async function sync(opts) {
152
166
  console.log(body(`The record lives on your system's overview:`));
153
167
  console.log(body(` ${base}/dashboard/mine/${slug}`));
154
168
  }
169
+ /**
170
+ * MEDE DE NOVO, AVISA, E ENVIA - o corpo do `sync` que faz um leitor novo chegar num sistema.
171
+ *
172
+ * A ordem é a lei aqui, e cada passo existe por um caso real:
173
+ *
174
+ * 1. a plataforma devolve o ESCOPO e a LEITURA já gravados. Sem isto, o `sync` teria que
175
+ * perguntar de novo o que já foi decidido, e um agente reescreveria 261 anatomias.
176
+ * 2. mede com o leitor ATUAL - é o único passo que precisa estar nesta máquina, porque o fonte
177
+ * dele não sai daqui.
178
+ * 3. funde a leitura gravada no censo fresco. A anatomia é do agente; a medição é nossa.
179
+ * 4. AVISA antes de enviar o que seria reescrito, e pergunta. Avisar depois de escrever não é
180
+ * avisar.
181
+ * 5. envia para a rota que atualiza o RASCUNHO pelo slug - nada publicado se move.
182
+ */
183
+ async function remeasure(args) {
184
+ const { root, base, token, slug } = args;
185
+ const head = await fetch(`${base}/api/registry/ds/${slug}/reading`, {
186
+ headers: { Authorization: `Bearer ${token}` },
187
+ }).catch(() => null);
188
+ if (!head?.ok) {
189
+ console.log(section("Sync"));
190
+ console.log(body(head
191
+ ? `The registry answered ${head.status} for this system's reading, so nothing was re-measured. The record below still travels.`
192
+ : "Could not reach the registry, so nothing was re-measured. The record below still travels."));
193
+ return;
194
+ }
195
+ const stored = (await head.json());
196
+ /**
197
+ * SEM CENSO GRAVADO NÃO HÁ O QUE REMEDIR CONTRA. Um sistema que nasceu da galeria não tem escopo
198
+ * nem leitura, e medir o repo dele produziria um censo de um projeto que ninguém mapeou para ele.
199
+ */
200
+ if (!stored.hasCensus) {
201
+ console.log(section("Sync"));
202
+ console.log(body(`"${slug}" did not come from an import, so there is no measurement to refresh. Import it first: npx synthesisui import`));
203
+ return;
204
+ }
205
+ console.log(section("Measuring your repository again"));
206
+ console.log(body(paint.faint(`${stored.scope ? `scope ${stored.scope}` : "the whole repo"} · the reading you already authored travels unchanged`)));
207
+ const census = await takeCensus(stored.scope ? join(root, stored.scope) : root, {
208
+ ...(stored.scope ? { scopeLabel: stored.scope } : {}),
209
+ ...(args.cli ? { cli: args.cli } : {}),
210
+ /** O `--usage` é EVIDÊNCIA e leva rótulo: sem ele o censo não sabe de que app o número veio. */
211
+ usage: (stored.usage ?? []).map((u) => ({
212
+ path: join(root, u),
213
+ label: u,
214
+ })),
215
+ }).catch(() => null);
216
+ if (!census) {
217
+ console.log(body("The measurement failed, so nothing was sent."));
218
+ return;
219
+ }
220
+ /** A LEITURA GRAVADA ENTRA NO CENSO FRESCO - ver a rota `reading`. */
221
+ if (stored.reading)
222
+ census.reading = stored.reading;
223
+ await resolveReadParts(census, root).catch(() => { });
224
+ /**
225
+ * O AVISO, ANTES DE ENVIAR. `edited` são as receitas que uma pessoa escreveu na tela; o cruzamento
226
+ * com o que acabou de ser medido é uma interseção de conjuntos, e é por isso que ele mora aqui em
227
+ * vez de virar um gêmeo da regra que vive na plataforma.
228
+ */
229
+ const slugOf = (name) => name
230
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
231
+ .toLowerCase()
232
+ .replace(/[^a-z0-9]+/g, "-")
233
+ .replace(/^-|-$/g, "");
234
+ const arriving = new Set(Object.keys(census.looks ?? {}).map(slugOf));
235
+ /**
236
+ * SÓ O QUE ESTE ENVIO TOCA. Uma receita que a pessoa editou e que a medição não traz fica onde
237
+ * está, e listá-la seria assustar sem motivo - o erro mais fácil de cometer neste aviso.
238
+ */
239
+ const hit = (stored.draft?.edited ?? []).filter((e) => arriving.has(e.component));
240
+ if (hit.length > 0) {
241
+ console.log("");
242
+ console.log(section("You have an active draft"));
243
+ console.log(body(stored.draft?.warning ??
244
+ `${hit.length} of the ${stored.draft?.components ?? "?"} components in it were written on the screen, and this send would rewrite them.`));
245
+ console.log(body(paint.faint(hit
246
+ .slice(0, 6)
247
+ .map((e) => `${e.component}${e.at ? ` (${e.at.slice(0, 10)})` : ""}`)
248
+ .join(" · "))));
249
+ if (!(await askToOverwrite())) {
250
+ console.log(body("Nothing was sent. Your draft is untouched."));
251
+ return;
252
+ }
253
+ }
254
+ const res = await fetch(`${base}/api/registry/ds/${slug}/census`, {
255
+ method: "POST",
256
+ headers: {
257
+ "content-type": "application/json",
258
+ Authorization: `Bearer ${token}`,
259
+ },
260
+ body: JSON.stringify({ census }),
261
+ }).catch(() => null);
262
+ if (!res?.ok) {
263
+ console.log(body(res
264
+ ? `The registry answered ${res.status}. Nothing was written.`
265
+ : "Could not reach the registry. Nothing was written."));
266
+ return;
267
+ }
268
+ const out = (await res.json());
269
+ console.log("");
270
+ console.log(section("Sent"));
271
+ console.log(body(`${out.written ?? 0} of ${out.total ?? 0} components written.`));
272
+ for (const note of out.notes ?? [])
273
+ console.log(body(paint.faint(note)));
274
+ if (out.url)
275
+ console.log(body(out.url));
276
+ }
277
+ /**
278
+ * Pergunta uma vez, e só num terminal de verdade.
279
+ *
280
+ * Sem TTY a resposta é NÃO: uma invocação por script que sobrescreve trabalho de alguém porque
281
+ * ninguém estava lá para dizer não é o pior default possível. `--yes` é o caminho explícito.
282
+ */
283
+ async function askToOverwrite() {
284
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
285
+ return false;
286
+ const { createInterface } = await import("node:readline/promises");
287
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
288
+ try {
289
+ const answer = await rl.question(" send anyway? [y/N]: ");
290
+ return /^y(es)?$/i.test(answer.trim());
291
+ }
292
+ catch {
293
+ return false;
294
+ }
295
+ finally {
296
+ rl.close();
297
+ }
298
+ }
package/dist/index.js CHANGED
@@ -54,8 +54,10 @@ Usage - governance (deterministic, FREE):
54
54
  something no reader would change
55
55
  synthesisui request [component|token] the queue of what your agent needed and the system
56
56
  refused to invent (--done <id> closes one)
57
- synthesisui sync send the local record - checks, fixes, open requests -
58
- to your system's dashboard (by hand, never automatic)
57
+ synthesisui sync re-measure this repo with the current reader and send it,
58
+ reusing the reading you already authored, plus the local
59
+ record - checks, fixes, open requests (--record-only skips
60
+ the measurement, --yes skips the overwrite question)
59
61
  synthesisui hook the check itself; installed by connect, run by your editor
60
62
  synthesisui mcp the system as tools; installed by connect, run by your agent
61
63
 
@@ -422,8 +424,21 @@ async function main() {
422
424
  await clean({ dir, force: flags.force === true });
423
425
  break;
424
426
  case "sync":
425
- // A versão que reporta o estado do repo - ver `repoStateOf`.
426
- await sync({ dir, registry, cli: CLI_VERSION });
427
+ /**
428
+ * A versão que reporta o estado do repo - ver `repoStateOf` - e que MEDE: desde 05/08 o `sync`
429
+ * remede o repo com o leitor atual e reenvia, reusando a leitura já gravada. É o único comando
430
+ * do lado do cliente para fazer um conserto de leitura chegar.
431
+ *
432
+ * `--record-only` manda só o registro, e `--yes` não pergunta antes de sobrescrever receita
433
+ * escrita na tela. O default é medir e perguntar.
434
+ */
435
+ await sync({
436
+ dir,
437
+ registry,
438
+ cli: CLI_VERSION,
439
+ recordOnly: args.includes("--record-only"),
440
+ yes: args.includes("--yes"),
441
+ });
427
442
  break;
428
443
  case "request": {
429
444
  // `synthesisui request` lists; `request component --name x --for "..."`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.140",
3
+ "version": "0.16.141",
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": {