synthesisui 0.16.177 → 0.16.179

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.
@@ -25,21 +25,45 @@ import { detectStack } from "../stack.js";
25
25
  *
26
26
  * Escrito uma vez e nunca reescrito: um arquivo que alguém ajustou é decisão dele.
27
27
  */
28
+ /**
29
+ * O que NÃO é do repositório: a medição e o registro local. Ambos são reescritos por inteiro a cada
30
+ * rodada e conflitariam em todo merge; o `.ledger-sent` descreve este clone e mais nada.
31
+ */
32
+ const IGNORED = [
33
+ "census.json",
34
+ "ledger.jsonl",
35
+ ".ledger-sent",
36
+ "not-expressed.md",
37
+ ];
38
+ const IGNORE_HEADER = "# Managed by synthesisui. The identity and the CSS are committed so a fresh\n" +
39
+ "# clone is governed; the measurement and the local record are not, because\n" +
40
+ "# both are rewritten in full on every run and would conflict on every merge.\n";
28
41
  async function writeGovernanceIgnore(projectRoot) {
29
42
  const path = join(projectRoot, "_synthesisui", ".gitignore");
30
- try {
31
- await access(path);
43
+ const existing = await readFile(path, "utf8").catch(() => null);
44
+ /**
45
+ * O ARQUIVO DIZ "Managed by synthesisui", E ELE PRECISA SER.
46
+ *
47
+ * Isto saía na primeira linha quando o arquivo já existia, então ele era escrito UMA vez e nunca
48
+ * mais. A consequência aparece na primeira vez que a gente acrescenta um registro local: em 07/08
49
+ * nasceu o `.ledger-sent`, e todo repositório que já tinha o `.gitignore` passaria a commitar um
50
+ * cursor que descreve a máquina de UMA pessoa. Um arquivo que se declara gerenciado e não é
51
+ * reconciliado é uma promessa quebrada com o time de alguém.
52
+ *
53
+ * ACRESCENTA, NUNCA REMOVE: uma linha que a pessoa escreveu ali é decisão dela, e some no dia em
54
+ * que a gente reescrevesse o arquivo inteiro. E só grava quando falta algo - um comando que sempre
55
+ * produz diff é um comando que o time para de rodar.
56
+ */
57
+ if (existing == null) {
58
+ await writeFile(path, `${IGNORE_HEADER}${IGNORED.map((l) => `${l}\n`).join("")}`, "utf8");
32
59
  return;
33
60
  }
34
- catch {
35
- // não existe - escrever
36
- }
37
- await writeFile(path, "# Managed by synthesisui. The identity and the CSS are committed so a fresh\n" +
38
- "# clone is governed; the measurement and the local record are not, because\n" +
39
- "# both are rewritten in full on every run and would conflict on every merge.\n" +
40
- "census.json\n" +
41
- "ledger.jsonl\n" +
42
- "not-expressed.md\n", "utf8");
61
+ const lines = new Set(existing.split("\n").map((l) => l.trim()));
62
+ const missing = IGNORED.filter((l) => !lines.has(l));
63
+ if (missing.length === 0)
64
+ return;
65
+ const sep = existing.endsWith("\n") ? "" : "\n";
66
+ await writeFile(path, `${existing}${sep}${missing.map((l) => `${l}\n`).join("")}`, "utf8");
43
67
  }
44
68
  /**
45
69
  * APAGA A PROSA QUE ESTA VERSÃO DEIXOU DE ESCREVER.
@@ -2,7 +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 { readToken, resolveRegistry } from "../config.js";
5
- import { readEvents } from "../doctor/ledger.js";
5
+ import { unsentEvents } from "../doctor/ledger.js";
6
6
  import { measuredScope } from "../measured-scope.js";
7
7
  import { READER } from "../reader-version.js";
8
8
  /**
@@ -124,7 +124,12 @@ opts = {}) {
124
124
  */
125
125
  run: "npx synthesisui sync",
126
126
  });
127
- const events = await readEvents(root).catch(() => []);
127
+ /**
128
+ * SÓ O QUE NÃO SUBIU - ver `unsentEvents`. Isto contava o arquivo inteiro, e como o ledger é
129
+ * append-only e o `sync` manda tudo (a plataforma deduplica), a linha nunca mais saía da tela e o
130
+ * número só crescia.
131
+ */
132
+ const events = await unsentEvents(root).catch(() => []);
128
133
  if (events.length > 0)
129
134
  out.push({
130
135
  says: `${events.length} checked write${events.length === 1 ? "" : "s"} recorded here and not sent - the platform is scoring this repo on older evidence.`,
@@ -1,7 +1,7 @@
1
1
  import { readdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { join, resolve } from "node:path";
3
3
  import { readToken, resolveRegistry } from "../config.js";
4
- import { readEvents } from "../doctor/ledger.js";
4
+ import { markSent, readEvents } from "../doctor/ledger.js";
5
5
  import { checkableName, closeRequest, readRequests, verifyAndCloseRequests, } from "../doctor/requests.js";
6
6
  import { measuredScope, rememberScope } from "../measured-scope.js";
7
7
  import { body, paint, section, snippet } from "../output.js";
@@ -114,6 +114,11 @@ export async function sync(opts) {
114
114
  console.log(body(err.message ?? `The registry answered ${res.status}.`));
115
115
  return;
116
116
  }
117
+ /**
118
+ * O QUE SUBIU, ANOTADO - senão o `align` continua pedindo o envio do que acabou de ser enviado.
119
+ * Depois da resposta OK, e nunca antes: marcar um envio que falhou perderia os eventos de vez.
120
+ */
121
+ await markSent(root, events);
117
122
  const out = (await res.json());
118
123
  console.log(section("Sync"));
119
124
  console.log(body(`${out.eventsReceived} checks sent, ${out.eventsNew} new. ${out.requestsNow} open request${out.requestsNow === 1 ? "" : "s"}.`));
@@ -46,6 +46,55 @@ export async function appendEvent(root, event) {
46
46
  // is a ledger that gets the hook uninstalled.
47
47
  }
48
48
  }
49
+ /**
50
+ * ATÉ ONDE ESTE REPO JÁ MANDOU - e a ausência disto fazia o `align` mentir para sempre.
51
+ *
52
+ * O ledger é APPEND-ONLY e o `sync` manda o arquivo inteiro; a plataforma deduplica (em 07/08 uma
53
+ * rodada reportou "6 checks sent, 1 new"). Só que o `align` contava TODAS as linhas e dizia "6
54
+ * checked writes recorded here and not sent" - sobre cinco que tinham subido minutos antes. O número
55
+ * só crescia, então aquela linha nunca mais sairia da tela, e um aviso permanente é um aviso que a
56
+ * pessoa aprende a pular (dono, 07/08: *"quer apostar que você vai achar algo errado?"*).
57
+ *
58
+ * A CONTAGEM E A HORA, as duas: a contagem responde sozinha enquanto o arquivo só cresce, e a hora
59
+ * dá como perceber que ele foi trocado por outro - aí a contagem antiga não vale mais nada.
60
+ */
61
+ const SENT_FILE = ".ledger-sent";
62
+ const sentPath = (root) => join(root, "_synthesisui", SENT_FILE);
63
+ export async function readSent(root) {
64
+ const raw = await readFile(sentPath(root), "utf8").catch(() => "");
65
+ if (!raw)
66
+ return { count: 0 };
67
+ try {
68
+ const v = JSON.parse(raw);
69
+ return {
70
+ count: typeof v.count === "number" && v.count >= 0 ? v.count : 0,
71
+ ...(typeof v.at === "string" ? { at: v.at } : {}),
72
+ };
73
+ }
74
+ catch {
75
+ return { count: 0 };
76
+ }
77
+ }
78
+ export async function markSent(root, events) {
79
+ const last = events[events.length - 1]?.at;
80
+ await writeFile(sentPath(root), `${JSON.stringify({ count: events.length, ...(last ? { at: last } : {}) })}\n`, "utf8").catch(() => {
81
+ /** O registro nunca pode custar o envio: o pior caso é o aviso reaparecer uma vez. */
82
+ });
83
+ }
84
+ /**
85
+ * Os eventos que este repo ainda não mandou.
86
+ *
87
+ * Se a contagem gravada é maior que o arquivo, alguém o trocou ou apagou - e aí a marca não descreve
88
+ * mais este arquivo. Contar tudo de novo é a leitura honesta: um envio a mais custa nada, e a
89
+ * plataforma deduplica.
90
+ */
91
+ export async function unsentEvents(root) {
92
+ const all = await readEvents(root);
93
+ const { count } = await readSent(root);
94
+ if (count <= 0 || count > all.length)
95
+ return all;
96
+ return all.slice(count);
97
+ }
49
98
  export async function readEvents(root) {
50
99
  const raw = await readFile(ledgerPath(root), "utf8").catch(() => "");
51
100
  if (!raw)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.177",
3
+ "version": "0.16.179",
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": {