synthesisui 0.16.174 → 0.16.177
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.
- package/dist/claude-md.js +6 -2
- package/dist/commands/add.js +23 -2
- package/dist/commands/align.js +50 -0
- package/dist/commands/connect.js +46 -17
- package/dist/doctor/style-ledger.js +2 -0
- package/dist/reader-version.js +24 -0
- package/package.json +1 -1
package/dist/claude-md.js
CHANGED
|
@@ -412,6 +412,7 @@ export async function syncClaudeMd(projectRoot) {
|
|
|
412
412
|
const installed = await readInstalled(projectRoot);
|
|
413
413
|
const region = await renderRegion(projectRoot, installed);
|
|
414
414
|
let createdAny = false;
|
|
415
|
+
const changed = [];
|
|
415
416
|
for (const home of HOMES) {
|
|
416
417
|
const path = join(projectRoot, home.path);
|
|
417
418
|
let existing = null;
|
|
@@ -435,6 +436,7 @@ export async function syncClaudeMd(projectRoot) {
|
|
|
435
436
|
await mkdir(dirname(path), { recursive: true }).catch(() => { });
|
|
436
437
|
await writeFile(path, `${home.frontmatter ?? ""}${region}\n`, "utf8");
|
|
437
438
|
createdAny = true;
|
|
439
|
+
changed.push(home.path);
|
|
438
440
|
continue;
|
|
439
441
|
}
|
|
440
442
|
const startIdx = existing.indexOf(START);
|
|
@@ -450,10 +452,12 @@ export async function syncClaudeMd(projectRoot) {
|
|
|
450
452
|
const sep = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
451
453
|
next = `${existing}${sep}${region}\n`;
|
|
452
454
|
}
|
|
453
|
-
if (next !== existing)
|
|
455
|
+
if (next !== existing) {
|
|
454
456
|
await writeFile(path, next, "utf8");
|
|
457
|
+
changed.push(home.path);
|
|
458
|
+
}
|
|
455
459
|
}
|
|
456
|
-
return { created: createdAny, count: installed.length };
|
|
460
|
+
return { created: createdAny, count: installed.length, changed };
|
|
457
461
|
}
|
|
458
462
|
/**
|
|
459
463
|
* Onde o bloco JÁ está, para o `connect` poder dizer isso em vez de ficar calado.
|
package/dist/commands/add.js
CHANGED
|
@@ -130,18 +130,39 @@ export async function add(slug, opts) {
|
|
|
130
130
|
const measured = await censusScope(projectRoot);
|
|
131
131
|
const scope = measured.system ?? prev?.scope ?? null;
|
|
132
132
|
const usage = measured.usage.length > 0 ? measured.usage : (prev?.usage ?? []);
|
|
133
|
-
|
|
133
|
+
/**
|
|
134
|
+
* `fetchedAt` SÓ ANDA QUANDO ALGO ATERROU - e a alternativa sujava o git de um time inteiro.
|
|
135
|
+
*
|
|
136
|
+
* O `.lock` é commitado, e este era o único campo não determinístico dele: tudo mais é função da
|
|
137
|
+
* versão, do compilador e das regras. Então cada `connect` de cada pessoa produzia um diff mesmo
|
|
138
|
+
* quando nada tinha mudado, e um comando que sempre gera commit é um comando que as pessoas param
|
|
139
|
+
* de rodar (dono, 07/08).
|
|
140
|
+
*
|
|
141
|
+
* O campo continua significando o que ele diz - quando esta instalação chegou -, e `repo-state` o
|
|
142
|
+
* manda para a plataforma como `installedAt`. Se nada chegou, ela não chegou de novo.
|
|
143
|
+
*/
|
|
144
|
+
const identity = {
|
|
134
145
|
slug: payload.slug,
|
|
135
146
|
name: payload.name,
|
|
136
147
|
version: payload.version,
|
|
137
148
|
registry: base,
|
|
138
|
-
fetchedAt: new Date().toISOString(),
|
|
139
149
|
...(opts.cli ? { cli: opts.cli } : {}),
|
|
140
150
|
/** Ver `RegistryPayload.compiler`: é o que faz um conserto de CSS chegar a um install. */
|
|
141
151
|
...(payload.compiler != null ? { compiler: payload.compiler } : {}),
|
|
152
|
+
...(payload.rulesStamp ? { rules: payload.rulesStamp } : {}),
|
|
142
153
|
...(scope ? { scope } : {}),
|
|
143
154
|
...(usage.length > 0 ? { usage } : {}),
|
|
144
155
|
};
|
|
156
|
+
const landed = (() => {
|
|
157
|
+
if (!prev?.fetchedAt)
|
|
158
|
+
return true;
|
|
159
|
+
const { fetchedAt: _was, ...before } = prev;
|
|
160
|
+
return JSON.stringify(before) !== JSON.stringify(identity);
|
|
161
|
+
})();
|
|
162
|
+
const lock = {
|
|
163
|
+
...identity,
|
|
164
|
+
fetchedAt: landed || !prev?.fetchedAt ? new Date().toISOString() : prev.fetchedAt,
|
|
165
|
+
};
|
|
145
166
|
await writeFile(rootLockPath, `${JSON.stringify(lock, null, 2)}\n`, "utf8");
|
|
146
167
|
await writeGovernanceIgnore(projectRoot);
|
|
147
168
|
const retired = await retireMaterializedDoctrine(slugDir);
|
package/dist/commands/align.js
CHANGED
|
@@ -4,6 +4,25 @@ import { join, resolve } from "node:path";
|
|
|
4
4
|
import { readToken, resolveRegistry } from "../config.js";
|
|
5
5
|
import { readEvents } from "../doctor/ledger.js";
|
|
6
6
|
import { measuredScope } from "../measured-scope.js";
|
|
7
|
+
import { READER } from "../reader-version.js";
|
|
8
|
+
/**
|
|
9
|
+
* Qual leitor mediu o censo em disco, quando ele o registra.
|
|
10
|
+
*
|
|
11
|
+
* `null` para censo anterior a esta marca: um censo que não diz quem o leu não prova nada, e supor
|
|
12
|
+
* que ele é velho faria toda pessoa que ainda não re-mediu ver o aviso para sempre.
|
|
13
|
+
*/
|
|
14
|
+
async function censusReader(root) {
|
|
15
|
+
const raw = await readFile(join(root, "_synthesisui", "census.json"), "utf8").catch(() => null);
|
|
16
|
+
if (!raw)
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
const c = JSON.parse(raw);
|
|
20
|
+
return typeof c.ledger?.reader === "number" ? c.ledger.reader : null;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
7
26
|
async function locksIn(root) {
|
|
8
27
|
const dsDir = join(root, "_synthesisui", "ds");
|
|
9
28
|
const entries = await readdir(dsDir, { withFileTypes: true }).catch(() => []);
|
|
@@ -69,6 +88,23 @@ opts = {}) {
|
|
|
69
88
|
says: `Your session is for ${creds.registry} and this system came from ${lock.registry}. That answers 401, which reads as an expired session on a host that never issued one.`,
|
|
70
89
|
run: `npx synthesisui login --registry ${lock.registry}`,
|
|
71
90
|
});
|
|
91
|
+
/**
|
|
92
|
+
* A MEDIÇÃO LIDA POR UM LEITOR ANTERIOR - e este era o quarto lado, sem cobertura nenhuma.
|
|
93
|
+
*
|
|
94
|
+
* Metade da esteira roda na máquina de quem tem o código, e o servidor não tem os arquivos: quando
|
|
95
|
+
* `transcribe`, `sketch` ou a derivação de anatomia melhoram, o censo guardado continua sendo a
|
|
96
|
+
* leitura de antes e NENHUMA re-interpretação nossa alcança isso. Só uma medição nova alcança, e
|
|
97
|
+
* ela é um comando que ninguém tinha motivo para rodar.
|
|
98
|
+
*
|
|
99
|
+
* Compara `READER`, não a string do CLI: em 07/08 o CLI subiu quatro vezes e nenhuma delas mudou um
|
|
100
|
+
* leitor - pedir re-medição em cada bump faria a pessoa parar de ler estas linhas.
|
|
101
|
+
*/
|
|
102
|
+
const reader = await censusReader(root);
|
|
103
|
+
if (reader != null && reader !== READER)
|
|
104
|
+
out.push({
|
|
105
|
+
says: `the measurement stored in this repo was read by an older reader, so what the platform knows about your components is what that reader could see. A re-measure is the only thing that reaches it - the fix lives on this machine, not on the server.`,
|
|
106
|
+
run: "npx synthesisui sync",
|
|
107
|
+
});
|
|
72
108
|
/**
|
|
73
109
|
* O ESCOPO, que é o desalinho mais caro e o mais silencioso: sem ele o `sync` mede o repo inteiro
|
|
74
110
|
* e manda os componentes de todos os apps para um sistema que é uma biblioteca (06/08, 338 num
|
|
@@ -169,6 +205,20 @@ export async function versionBehind(root, opts = {}) {
|
|
|
169
205
|
says: `the css for "${lock.slug}" v${lock.version} is compiled differently now - same version, same document, a fix on our side. The files in this repo were written before it.`,
|
|
170
206
|
run: "npx synthesisui connect",
|
|
171
207
|
};
|
|
208
|
+
/**
|
|
209
|
+
* AS REGRAS MUDARAM - e nem a versão nem o CLI diziam isso.
|
|
210
|
+
*
|
|
211
|
+
* A governança de um sistema não é versionada com o documento: `listDsRuleSetByDsId` lê por
|
|
212
|
+
* SISTEMA, então uma lei escrita na plataforma vale no instante seguinte. O `doctrine.json` em
|
|
213
|
+
* disco, que é o que o agente lê com `system_doctrine`, só é reescrito por um `add`. Então quem
|
|
214
|
+
* cuida do sistema escrevia uma lei e o agente de quem o consome continuava obedecendo as de
|
|
215
|
+
* ontem, sem sinal nenhum (medido em 07/08).
|
|
216
|
+
*/
|
|
217
|
+
if (body.rulesStamp && body.rulesStamp !== (lock.rules ?? null))
|
|
218
|
+
return {
|
|
219
|
+
says: `the rules that govern "${lock.slug}" changed since this repo materialized them - your agent reads the copy on disk, so it is still following the previous set.`,
|
|
220
|
+
run: "npx synthesisui connect",
|
|
221
|
+
};
|
|
172
222
|
return null;
|
|
173
223
|
}
|
|
174
224
|
/**
|
package/dist/commands/connect.js
CHANGED
|
@@ -49,13 +49,12 @@ const LEGACY_SKILLS = ["import-design-system"];
|
|
|
49
49
|
* SILENCIOSO QUANDO JÁ ESTÁ EM DIA, e sem rede não faz nada - um `connect` que falha por estar num
|
|
50
50
|
* avião seria pior que a defasagem que ele conserta.
|
|
51
51
|
*/
|
|
52
|
-
/** O
|
|
53
|
-
async function
|
|
52
|
+
/** O compilador e as regras que serviriam esta versão HOJE - vazio quando não dá para saber. */
|
|
53
|
+
async function fetchMeta(base, slug, version) {
|
|
54
54
|
const res = await fetch(`${base}/api/registry/ds/${slug}?version=${version}&meta=1`).catch(() => null);
|
|
55
55
|
if (!res?.ok)
|
|
56
|
-
return
|
|
57
|
-
|
|
58
|
-
return typeof body?.compiler === "number" ? body.compiler : null;
|
|
56
|
+
return {};
|
|
57
|
+
return ((await res.json().catch(() => null)) ?? {});
|
|
59
58
|
}
|
|
60
59
|
async function refreshInstall(root, cli, registry) {
|
|
61
60
|
const dsDir = join(root, "_synthesisui", "ds");
|
|
@@ -89,9 +88,11 @@ async function refreshInstall(root, cli, registry) {
|
|
|
89
88
|
*/
|
|
90
89
|
/** O host que emitiu ESTE install manda - um token pertence ao host que o emitiu. */
|
|
91
90
|
const base = resolveRegistry(registry ?? lock.registry);
|
|
92
|
-
const remote = await
|
|
93
|
-
const compilerMoved = remote != null && remote !== (lock.compiler ?? null);
|
|
94
|
-
|
|
91
|
+
const remote = await fetchMeta(base, lock.slug, lock.version);
|
|
92
|
+
const compilerMoved = remote.compiler != null && remote.compiler !== (lock.compiler ?? null);
|
|
93
|
+
/** Regras valem no instante em que são escritas - ver `align`, e `rulesStamp` na plataforma. */
|
|
94
|
+
const rulesMoved = remote.rulesStamp != null && remote.rulesStamp !== (lock.rules ?? null);
|
|
95
|
+
if (lock.cli === cli && !compilerMoved && !rulesMoved)
|
|
95
96
|
continue;
|
|
96
97
|
const done = await add(lock.slug, {
|
|
97
98
|
...(registry ? { registry } : {}),
|
|
@@ -172,7 +173,7 @@ export async function connect(opts) {
|
|
|
172
173
|
const wired = await wireAgent(root, opts.version, want);
|
|
173
174
|
// The block reads the settings we just wrote, so it must be regenerated
|
|
174
175
|
// after them, not before.
|
|
175
|
-
await syncClaudeMd(root);
|
|
176
|
+
const contract = await syncClaudeMd(root);
|
|
176
177
|
console.log(section("Connected"));
|
|
177
178
|
/**
|
|
178
179
|
* O QUE ESTE CLI REESCREVEU NA PASTA DO SISTEMA - dito primeiro, porque é o que a pessoa não sabia
|
|
@@ -230,8 +231,18 @@ export async function connect(opts) {
|
|
|
230
231
|
* qual ferramenta ela usa. Dizer os nomes é o que faz um time descobrir que a governança já chegou
|
|
231
232
|
* no editor dele: até 05/08 ela só alcançava quem usa Claude Code, e ninguém tinha como saber.
|
|
232
233
|
*/
|
|
233
|
-
|
|
234
|
-
|
|
234
|
+
/**
|
|
235
|
+
* `✓` SÓ PARA O QUE MUDOU - ver `syncClaudeMd.changed`. Esta linha era a única do bloco que
|
|
236
|
+
* marcava mudança sempre, e um `connect` seguido dizia "rewritten" sobre um arquivo intocado.
|
|
237
|
+
*/
|
|
238
|
+
for (const home of await blockHomes(root)) {
|
|
239
|
+
const moved = contract.changed.includes(home);
|
|
240
|
+
console.log(body(`${moved ? "✓" : "·"} ${home.padEnd(22)} ${moved
|
|
241
|
+
? home === "CLAUDE.md"
|
|
242
|
+
? "rewritten for what is installed"
|
|
243
|
+
: "the same rules, where this agent reads them"
|
|
244
|
+
: "already says what is installed"}`));
|
|
245
|
+
}
|
|
235
246
|
/**
|
|
236
247
|
* The fourth layer, and the one that had no installer at all: the import
|
|
237
248
|
* skill existed only in our own repo, so the only way to get it was to clone
|
|
@@ -307,12 +318,30 @@ export async function connect(opts) {
|
|
|
307
318
|
console.log(paint.blue(snippet(["npx synthesisui@latest ci"])));
|
|
308
319
|
}
|
|
309
320
|
await offerShellHook(opts.shell === true);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
321
|
+
/**
|
|
322
|
+
* O REINÍCIO SÓ QUANDO ELE É NECESSÁRIO - e pedi-lo sempre é o que fez o dono achar que reiniciar
|
|
323
|
+
* o editor fazia parte do fluxo (07/08).
|
|
324
|
+
*
|
|
325
|
+
* Hooks e servidores MCP são lidos UMA vez, quando a sessão abre - se um deles mudou, a sessão em
|
|
326
|
+
* curso está rodando o anterior e não há como avisá-la. Mas `connect` também roda quando só o CSS
|
|
327
|
+
* foi re-materializado, e aí não há nada em memória para atualizar: os arquivos são lidos pelo
|
|
328
|
+
* build, não pelo editor.
|
|
329
|
+
*
|
|
330
|
+
* Um pedido que aparece sempre é um pedido que a pessoa passa a ignorar - e aí ele não serve para
|
|
331
|
+
* a vez em que era mesmo obrigatório.
|
|
332
|
+
*/
|
|
333
|
+
const moved = (s) => s === "added" || s === "updated";
|
|
334
|
+
const needsRestart = moved(wired.hook) ||
|
|
335
|
+
moved(wired.session) ||
|
|
336
|
+
(Array.isArray(wired.mcp) && wired.mcp.some((m) => moved(m.status)));
|
|
337
|
+
if (needsRestart) {
|
|
338
|
+
console.log("");
|
|
339
|
+
console.log(body("Reopen your editor session - all of them are read at startup."));
|
|
340
|
+
if (want.mcp &&
|
|
341
|
+
Array.isArray(wired.mcp) &&
|
|
342
|
+
wired.mcp.some((m) => moved(m.status))) {
|
|
343
|
+
console.log(body('A project MCP server needs approving once; say yes when it asks. Then "/mcp" lists synthesisui.'));
|
|
344
|
+
}
|
|
316
345
|
}
|
|
317
346
|
if (wired.command.startsWith("npx synthesisui@")) {
|
|
318
347
|
console.log("");
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* interpretação como um número que se move. Dizer 100% de cobertura existindo uma classe
|
|
27
27
|
* calculada em runtime seria exatamente a mentira que esta esteira existe para não contar.
|
|
28
28
|
*/
|
|
29
|
+
import { READER } from "../reader-version.js";
|
|
29
30
|
const SHAPES = [
|
|
30
31
|
"class",
|
|
31
32
|
"template",
|
|
@@ -97,6 +98,7 @@ export function buildLedger(cli, seen) {
|
|
|
97
98
|
}
|
|
98
99
|
return {
|
|
99
100
|
cli,
|
|
101
|
+
reader: READER,
|
|
100
102
|
counted,
|
|
101
103
|
interpreted,
|
|
102
104
|
unread: [...groups.values()]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A VERSÃO DO LEITOR - quem diz que uma medição já guardada ficou velha.
|
|
3
|
+
*
|
|
4
|
+
* Metade da esteira roda AQUI, na máquina de quem tem o código: `transcribe`, `sketch`,
|
|
5
|
+
* `anatomy-from-sketch`, os leitores de CSS Modules e de variantes. Quando um deles melhora, o censo
|
|
6
|
+
* gravado continua sendo a leitura antiga - e nenhuma re-interpretação no servidor alcança isso,
|
|
7
|
+
* porque o servidor não tem os arquivos. Só uma nova medição alcança, e ela é um comando: `sync`.
|
|
8
|
+
*
|
|
9
|
+
* O problema era saber QUANDO pedir. O censo grava a string do CLI que mediu, e o CLI sobe várias
|
|
10
|
+
* vezes por dia por motivos que não têm nada a ver com leitura - em 07/08 foram quatro versões, e
|
|
11
|
+
* nenhuma delas mudou um leitor. Comparar a string mandaria re-medir em todas, e um aviso que aparece
|
|
12
|
+
* à toa é um aviso que a pessoa aprende a não ler.
|
|
13
|
+
*
|
|
14
|
+
* Então este número existe e sobe SOZINHO, pelo mesmo critério do `COMPILER` na plataforma: quando os
|
|
15
|
+
* leitores passam a produzir um censo diferente para os MESMOS arquivos. Nunca por refactor, nunca por
|
|
16
|
+
* publicação, nunca por conserto que não toca leitura.
|
|
17
|
+
*
|
|
18
|
+
* As três marcas, e o que cada uma alcança:
|
|
19
|
+
*
|
|
20
|
+
* READER a medição na máquina dela -> npx synthesisui sync
|
|
21
|
+
* COMPILER o CSS de uma versão já instalada -> npx synthesisui connect
|
|
22
|
+
* INTERPRETATION a nossa metade, sobre censos já guardados -> roda no servidor, sem comando
|
|
23
|
+
*/
|
|
24
|
+
export const READER = 1;
|
package/package.json
CHANGED