synthesisui 0.16.419 → 0.16.421
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/agent-wiring.js +52 -8
- package/dist/changed-files.js +56 -0
- package/dist/commands/connect.js +33 -0
- package/dist/commands/doctor.js +28 -8
- package/dist/commands/hook.js +120 -28
- package/dist/commands/import.js +30 -7
- package/dist/commands/mcp.js +18 -4
- package/dist/commands/upgrade.js +17 -0
- package/dist/config.js +14 -0
- package/dist/copy/connect.pt-BR.js +12 -0
- package/dist/doctor/idiom-names.js +266 -0
- package/dist/doctor/ledger.js +9 -2
- package/dist/doctor/scan.js +38 -2
- package/dist/doctor/tokens.js +9 -1
- package/dist/governed.js +53 -0
- package/dist/guarantee.js +171 -0
- package/dist/install-marks.js +27 -5
- package/dist/merge-census.js +11 -2
- package/dist/outside-scope.js +100 -4
- package/dist/rule-touched.js +28 -38
- package/package.json +1 -1
package/dist/install-marks.js
CHANGED
|
@@ -226,7 +226,16 @@
|
|
|
226
226
|
*
|
|
227
227
|
* O que o cliente ganha ao rodar `upgrade`: a lista responde ao espaço, como a caixa promete.
|
|
228
228
|
*/
|
|
229
|
-
|
|
229
|
+
/**
|
|
230
|
+
* 0.16.416 -> 0.16.421 em 12/09, e o passo 1 deu **SIM**: muda um byte que cai na pasta dele.
|
|
231
|
+
*
|
|
232
|
+
* O filtro do hook em `.claude/settings.json` era `Write|Edit|MultiEdit` e passa a incluir a
|
|
233
|
+
* escrita por shell. Quem instalou antes tem no arquivo dele o filtro estreito que NÓS escrevemos,
|
|
234
|
+
* e sem rodar o comando ele fica com uma checagem que cala em toda edição em lote. O `upgrade`
|
|
235
|
+
* chama `wireAgent`, então rodar o comando é o caminho de volta - e ele só alarga a string que era
|
|
236
|
+
* nossa, nunca um filtro que uma pessoa escreveu.
|
|
237
|
+
*/
|
|
238
|
+
export const MATERIALISER_SINCE = "0.16.421";
|
|
230
239
|
/**
|
|
231
240
|
* A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
|
|
232
241
|
*
|
|
@@ -334,8 +343,8 @@ export const MATERIALISER_SINCE = "0.16.416";
|
|
|
334
343
|
* A frase viaja ao lado da versão porque as duas são uma coisa só: quem move a marca troca a
|
|
335
344
|
* explicação no mesmo lugar, em vez de deixar o CI citando a causa da marca anterior.
|
|
336
345
|
*/
|
|
337
|
-
export const COUNTED_DIFFERENTLY_SINCE = "0.16.
|
|
338
|
-
export const COUNTED_DIFFERENTLY = "this run counts
|
|
346
|
+
export const COUNTED_DIFFERENTLY_SINCE = "0.16.421";
|
|
347
|
+
export const COUNTED_DIFFERENTLY = "this run counts the utilities of your own theme - that version counted only `var(--token)`";
|
|
339
348
|
/**
|
|
340
349
|
* 0.16.408 -> 0.16.413 em 10/09: o hook passa a DIZER o valor que nada nomeia. Ele silenciava toda
|
|
341
350
|
* deriva sem token de destino - a decisão estava escrita como "não vale interromper, porque o único
|
|
@@ -344,7 +353,20 @@ export const COUNTED_DIFFERENTLY = "this run counts every length in a shorthand
|
|
|
344
353
|
* e nós engolimos". Agora sai `line 12 #ff00aa`, sem uma única proposta de nome nosso. Um hook
|
|
345
354
|
* pinado antes desta versão aconselha MENOS, que é a régua desta marca.
|
|
346
355
|
*/
|
|
347
|
-
|
|
356
|
+
/**
|
|
357
|
+
* 0.16.419 -> 0.16.421 em 12/09: o hook passa a rodar quando o ARQUIVO muda, e não só quando o
|
|
358
|
+
* agente usa a ferramenta de edição.
|
|
359
|
+
*
|
|
360
|
+
* MEDIDO no `codelevel`, três sessões: toda edição em lote passou por comando de shell e a
|
|
361
|
+
* checagem produziu **0 linhas**; na única edição feita pela ferramenta de edição ela falou na
|
|
362
|
+
* hora. Um hook pinado antes desta versão fica calado em toda escrita por shell, que é a forma
|
|
363
|
+
* como um agente edita em lote - aconselha MENOS, e é a régua desta marca.
|
|
364
|
+
*
|
|
365
|
+
* E ele passa a contar a utility do tema DELE como uso de token: num projeto cujo idioma é
|
|
366
|
+
* utility, um arquivo escrito inteiro no vocabulário do sistema recebia zero. A contagem em si é a
|
|
367
|
+
* outra marca - ver `COUNTED_DIFFERENTLY_SINCE`, que sobe no mesmo diff.
|
|
368
|
+
*/
|
|
369
|
+
export const CHECKER_SINCE = "0.16.421";
|
|
348
370
|
/**
|
|
349
371
|
* A ÚLTIMA VERSÃO EM QUE OS LEITORES PASSARAM A PRODUZIR UM CENSO DIFERENTE.
|
|
350
372
|
*
|
|
@@ -793,7 +815,7 @@ export const CHECKER_SINCE = "0.16.419";
|
|
|
793
815
|
* deveriam estar lá. Quem tem censo gravado precisa de um `sync` para a fila de trabalho dele ser a
|
|
794
816
|
* real; ver o livro-razão de `corpus.spec.ts`.
|
|
795
817
|
*/
|
|
796
|
-
export const READER_SINCE = "0.16.
|
|
818
|
+
export const READER_SINCE = "0.16.421";
|
|
797
819
|
/**
|
|
798
820
|
* O QUE ESTÁ INSTALADO AQUI FICOU PARA TRÁS - e as DUAS condições que fazem isso ser verdade.
|
|
799
821
|
*
|
package/dist/merge-census.js
CHANGED
|
@@ -237,11 +237,20 @@ export function mergeCensus(list) {
|
|
|
237
237
|
const named = totals.tokenUses + (totals.ownUses ?? 0);
|
|
238
238
|
const denominator = named + totals.values + (totals.phantomUses ?? 0);
|
|
239
239
|
const complete = list.every((c) => c.totals.ownUses !== undefined);
|
|
240
|
-
|
|
240
|
+
/**
|
|
241
|
+
* `0 DE 0` NÃO É NOTA MÁXIMA - a mesma correção que a régua recebeu em 12/09 (ver `Reach` em
|
|
242
|
+
* `doctor/scan.ts`). Fundir dois escopos sem um único valor de design devolvia `100`, e o campo
|
|
243
|
+
* agora some: quem lê distingue "nada veio do sistema" de "não havia o que contar".
|
|
244
|
+
*/
|
|
245
|
+
const share = !complete
|
|
241
246
|
? first.totals.coverage
|
|
242
247
|
: denominator > 0
|
|
243
248
|
? Math.round((named / denominator) * 100)
|
|
244
|
-
:
|
|
249
|
+
: undefined;
|
|
250
|
+
if (share === undefined)
|
|
251
|
+
delete totals.coverage;
|
|
252
|
+
else
|
|
253
|
+
totals.coverage = share;
|
|
245
254
|
const out = {
|
|
246
255
|
...first,
|
|
247
256
|
project: { ...first.project, stack },
|
package/dist/outside-scope.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readdir } from "node:fs/promises";
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { join, relative, sep } from "node:path";
|
|
3
3
|
/**
|
|
4
4
|
* O QUE O ESCOPO EXCLUIU - o único denominador que a esteira não tinha.
|
|
@@ -78,11 +78,26 @@ async function countIn(dir, root, inside, tally, depth = 0) {
|
|
|
78
78
|
* escopo" não é uma categoria que existe - e emitir uma linha de zero ensinaria o cliente a ignorar a
|
|
79
79
|
* seção quando ela tiver algo.
|
|
80
80
|
*/
|
|
81
|
-
export async function outsideScope(root, scopes
|
|
82
|
-
|
|
81
|
+
export async function outsideScope(root, scopes,
|
|
82
|
+
/**
|
|
83
|
+
* QUEM MAIS ESTÁ GOVERNADO, e quem ele tirou.
|
|
84
|
+
*
|
|
85
|
+
* Os dois vêm MEDIDOS ou DECLARADOS, nunca supostos: `consumers` é `census.adoption.consumers`,
|
|
86
|
+
* que a esteira produz ao ver quem importa a biblioteca; `ungoverned` é a lista que ELE escreveu
|
|
87
|
+
* em `_synthesisui/config.json`. Sem os dois, esta função responde exatamente o que respondia
|
|
88
|
+
* antes de a governança existir.
|
|
89
|
+
*/
|
|
90
|
+
also = {}) {
|
|
91
|
+
const clean = (s) => s.replace(/^\.\//, "").replace(/\/$/, "");
|
|
92
|
+
const declared = scopes.map(clean);
|
|
83
93
|
if (declared.length === 0)
|
|
84
94
|
return null;
|
|
85
|
-
const
|
|
95
|
+
const out = (also.ungoverned ?? []).map(clean);
|
|
96
|
+
const isOut = (rel) => out.some((s) => rel === s || rel.startsWith(`${s}/`));
|
|
97
|
+
/** Um consumidor que ele declarou fora volta a ser fora - a saída é dele. */
|
|
98
|
+
const consumers = (also.consumers ?? []).map(clean).filter((c) => !isOut(c));
|
|
99
|
+
const under = (paths, rel) => paths.some((s) => rel === s || rel.startsWith(`${s}/`));
|
|
100
|
+
const inside = (rel) => under(declared, rel);
|
|
86
101
|
const tally = new Map();
|
|
87
102
|
await countIn(root, root, inside, tally);
|
|
88
103
|
const places = [...tally]
|
|
@@ -91,6 +106,20 @@ export async function outsideScope(root, scopes) {
|
|
|
91
106
|
const files = places.reduce((n, p) => n + p.files, 0);
|
|
92
107
|
if (files === 0)
|
|
93
108
|
return null;
|
|
109
|
+
/**
|
|
110
|
+
* A GOVERNANÇA É UM CAMPO AO LADO, e NUNCA um desconto nesta conta - achado da revisão de
|
|
111
|
+
* contrato no fecho, e ele estava certo pelo motivo exato.
|
|
112
|
+
*
|
|
113
|
+
* A primeira versão tirava os consumidores de `files`/`places`, e isso misturava duas réguas num
|
|
114
|
+
* número só: um app que ninguém APONTOU continua não tendo sido LIDO, e esta conta existe para
|
|
115
|
+
* dizer o tamanho da porta fechada. Descontá-lo aqui fazia 26 arquivos de um repositório real
|
|
116
|
+
* saírem do denominador sem uma linha deles ter sido aberta - o oposto do que o cabeçalho
|
|
117
|
+
* promete.
|
|
118
|
+
*
|
|
119
|
+
* A separação é por LUGAR porque a unidade da frase é o lugar: `apps/landing` é o que ele
|
|
120
|
+
* reconhece, e `countIn` já agrupa por diretório de topo.
|
|
121
|
+
*/
|
|
122
|
+
const governed = places.filter((p) => under(consumers, p.path));
|
|
94
123
|
const named = places
|
|
95
124
|
.slice(0, 3)
|
|
96
125
|
.map((p) => `${p.path} (${p.files})`)
|
|
@@ -98,6 +127,73 @@ export async function outsideScope(root, scopes) {
|
|
|
98
127
|
return {
|
|
99
128
|
files,
|
|
100
129
|
places,
|
|
130
|
+
governed,
|
|
131
|
+
/** A frase é só sobre LEITURA - a da governança mora em `guarantee.ts`, e é outra régua. */
|
|
101
132
|
said: `${files} more readable file${files === 1 ? "" : "s"} live outside what you pointed at - ${named}${places.length > 3 ? `, and ${places.length - 3} more place${places.length - 3 === 1 ? "" : "s"}` : ""}. Nothing was read there, so nothing about them is in this measurement: point at them too if their design belongs in this system.`,
|
|
102
133
|
};
|
|
103
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* QUEM DEPENDE DESTA BIBLIOTECA, lido do arquivo em que o próprio gerenciador de pacotes decide.
|
|
137
|
+
*
|
|
138
|
+
* ═══ O QUE ISTO DESTRAVA ═══
|
|
139
|
+
*
|
|
140
|
+
* Os apps que consomem a biblioteca passam a estar governados SEM ninguém digitar um `--usage`.
|
|
141
|
+
* Medido em 12/09 no `codelevel`: o censo conhecia `apps/landing` e `apps/web` porque alguém os
|
|
142
|
+
* apontou à mão; num repositório onde ninguém apontou, os dois lugares onde a tela é escrita
|
|
143
|
+
* ficavam do lado de fora da garantia, ao lado de `packages/cli` e `packages/db`.
|
|
144
|
+
*
|
|
145
|
+
* ═══ POR QUE `package.json`, E NÃO UMA VARREDURA DE IMPORTS ═══
|
|
146
|
+
*
|
|
147
|
+
* Um app que escreve `import { Card } from "@repo/ui"` é um consumidor, e descobrir isso lendo
|
|
148
|
+
* todo arquivo do repositório custa a varredura inteira. O `package.json` dele já declara a
|
|
149
|
+
* dependência - é o que o gerenciador de pacotes lê para instalar -, e uma dependência declarada é
|
|
150
|
+
* evidência mais forte que uma string encontrada: ela é a intenção, não o rastro.
|
|
151
|
+
*
|
|
152
|
+
* O QUE ISTO NÃO ALCANÇA, declarado: um repositório de um pacote só, sem workspaces, não tem um
|
|
153
|
+
* segundo `package.json` para ler - e ali não há "consumidor" separado a governar, porque o escopo
|
|
154
|
+
* medido e o app são o mesmo lugar.
|
|
155
|
+
*/
|
|
156
|
+
export async function consumersOf(root, specifier) {
|
|
157
|
+
if (!specifier)
|
|
158
|
+
return [];
|
|
159
|
+
const found = [];
|
|
160
|
+
const visit = async (dir, depth) => {
|
|
161
|
+
if (depth > 3)
|
|
162
|
+
return;
|
|
163
|
+
let entries;
|
|
164
|
+
try {
|
|
165
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
for (const entry of entries) {
|
|
171
|
+
if (entry.name.startsWith(".") || SKIP.has(entry.name))
|
|
172
|
+
continue;
|
|
173
|
+
if (!entry.isDirectory())
|
|
174
|
+
continue;
|
|
175
|
+
const here = join(dir, entry.name);
|
|
176
|
+
const raw = await readFile(join(here, "package.json"), "utf8").catch(() => "");
|
|
177
|
+
if (raw) {
|
|
178
|
+
try {
|
|
179
|
+
const pkg = JSON.parse(raw);
|
|
180
|
+
const deps = ["dependencies", "devDependencies", "peerDependencies"]
|
|
181
|
+
.map((k) => pkg[k])
|
|
182
|
+
.filter((d) => !!d && typeof d === "object");
|
|
183
|
+
if (deps.some((d) => specifier in d)) {
|
|
184
|
+
const rel = relative(root, here);
|
|
185
|
+
/** O próprio pacote não é consumidor de si mesmo. */
|
|
186
|
+
if (rel && pkg.name !== specifier)
|
|
187
|
+
found.push(rel);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// Um `package.json` ilegível custa a descoberta daquele lugar, nunca a rodada.
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
await visit(here, depth + 1);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
await visit(root, 0);
|
|
198
|
+
return found.sort();
|
|
199
|
+
}
|
package/dist/rule-touched.js
CHANGED
|
@@ -1,40 +1,6 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
const run = promisify(execFile);
|
|
4
|
-
/**
|
|
5
|
-
* O QUE ESTA EDIÇÃO APAGOU, E QUAL REGRA DELE ISSO TOCA.
|
|
6
|
-
*
|
|
7
|
-
* ═══ O CASO, medido em 12/09 ═══
|
|
8
|
-
*
|
|
9
|
-
* O agente dele apagou a serif itálica de seis lugares da landing do `codelevel`. A regra existia,
|
|
10
|
-
* instalada: *"A display headline breaks onto a line set in the serif, italic - that line carries
|
|
11
|
-
* the emphasis of the sentence, never the whole headline."* Nada avisou.
|
|
12
|
-
*
|
|
13
|
-
* Duas razões, e este arquivo é as duas. A checagem lia o ARQUIVO depois da escrita - e uma
|
|
14
|
-
* remoção é, por construção, aquilo que não está mais lá. E ela media VALOR SOLTO contra token,
|
|
15
|
-
* nunca regra.
|
|
16
|
-
*
|
|
17
|
-
* ═══ O ANTES VEM DO GIT, e não do payload do cliente ═══
|
|
18
|
-
*
|
|
19
|
-
* O hook recebe `tool_input.file_path` e o cliente PODE mandar o texto anterior - mas o formato é
|
|
20
|
-
* de cada cliente, muda entre versões, e um produto que se apoia nele funciona num editor e cala
|
|
21
|
-
* no seguinte. O git é o mesmo em todo lugar e responde a pergunta certa: *o que esta árvore
|
|
22
|
-
* removeu em relação ao último commit?* - que é exatamente o que ele vai empurrar.
|
|
23
|
-
*
|
|
24
|
-
* SEM GIT, SEM ARQUIVO NO HEAD, OU ARQUIVO NOVO: não há "antes", então não há remoção. Silêncio é
|
|
25
|
-
* a resposta correta, e não uma falha - um arquivo novo não apagou nada de ninguém.
|
|
26
|
-
*
|
|
27
|
-
* ═══ E O CASAMENTO É POR VOCABULÁRIO DECLARADO, nunca por palavra em inglês ═══
|
|
28
|
-
*
|
|
29
|
-
* A tentação é procurar as palavras da regra no diff. Isso casaria *"never"* com qualquer coisa e
|
|
30
|
-
* transformaria a checagem numa fonte de falso positivo - e um falso positivo custa mais que
|
|
31
|
-
* silêncio aqui, porque ele treina a pessoa a ignorar a saída e depois a desinstalar o hook.
|
|
32
|
-
*
|
|
33
|
-
* Então os dois lados são amarrados pelo que o SISTEMA DELE DECLARA. Uma regra "toca" o que foi
|
|
34
|
-
* apagado quando um nome declarado aparece nos dois: no texto da regra e no que saiu do arquivo.
|
|
35
|
-
* No caso real: o sistema declara `--font-serif`, a regra fala em `serif`, e a edição removeu
|
|
36
|
-
* `font-serif` seis vezes.
|
|
37
|
-
*/
|
|
38
4
|
/** Como o texto anterior era, ou `null` quando não há "antes" que se possa provar. */
|
|
39
5
|
export async function previousText(root, rel) {
|
|
40
6
|
try {
|
|
@@ -78,11 +44,20 @@ export async function previousText(root, rel) {
|
|
|
78
44
|
* arquivo. É a troca certa. A regra fala sobre o sistema ter aquela decisão presente, e um uso que
|
|
79
45
|
* fica mantém a decisão presente; acusar por contagem transformaria toda refatoração num alarme.
|
|
80
46
|
*/
|
|
81
|
-
export function namesRemoved(before, after,
|
|
82
|
-
|
|
47
|
+
export function namesRemoved(before, after,
|
|
48
|
+
/**
|
|
49
|
+
* A TABELA INTEIRA, e não o conjunto de nomes - porta fechada em vez de vigiada.
|
|
50
|
+
*
|
|
51
|
+
* Ela era `declared: ReadonlySet<string>`, e quando a leitura por idioma entrou o chamador
|
|
52
|
+
* passaria a precisar de um segundo argumento. Um argumento a mais é um argumento esquecido: o
|
|
53
|
+
* comando leria as utilities dele e o hook não, e o mesmo repositório receberia duas respostas
|
|
54
|
+
* sobre a mesma regra.
|
|
55
|
+
*/
|
|
56
|
+
table) {
|
|
57
|
+
const had = declaredNamesIn(before, table);
|
|
83
58
|
if (had.length === 0)
|
|
84
59
|
return [];
|
|
85
|
-
const kept = new Set(declaredNamesIn(after,
|
|
60
|
+
const kept = new Set(declaredNamesIn(after, table));
|
|
86
61
|
return had.filter((name) => !kept.has(name));
|
|
87
62
|
}
|
|
88
63
|
/**
|
|
@@ -124,13 +99,28 @@ const mentions = (text, word) => new RegExp(`(^|[^a-z0-9-])${word}([^a-z0-9-]|$)
|
|
|
124
99
|
* `--font-serif` cru numa declaração, e `font-serif` como utility - que é o idioma medido em 72%
|
|
125
100
|
* dos `.tsx` dele. Sem a terceira, um repositório Tailwind inteiro não referenciaria nada.
|
|
126
101
|
*/
|
|
127
|
-
export function declaredNamesIn(text, declared) {
|
|
102
|
+
export function declaredNamesIn(text, { declared, utilities }) {
|
|
128
103
|
const found = new Set();
|
|
129
104
|
for (const name of declared) {
|
|
130
105
|
const bare = name.replace(/^--/, "");
|
|
131
106
|
if (text.includes(name) || mentions(text, bare))
|
|
132
107
|
found.add(name);
|
|
133
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* E A QUARTA FORMA, que era a maior lacuna - a utility que o idioma dele RENOMEIA.
|
|
111
|
+
*
|
|
112
|
+
* As três acima só fecham onde o nome do token É a string escrita (`--font-serif` →
|
|
113
|
+
* `font-serif`). Onde o compilador renomeia - `--color-primary` → `bg-primary`,
|
|
114
|
+
* `--spacing-6` → `p-6` - nada casava, e medido em 12/09 na população viva: **5 das 25 regras
|
|
115
|
+
* dele eram alcançadas**, todas por `--font-*`, `--animate-*`, `--ease-*`, `--shadow-sm`,
|
|
116
|
+
* `--text-sm`. Zero cor, zero espaçamento, zero raio - que é a maior classe de regra de qualquer
|
|
117
|
+
* design system.
|
|
118
|
+
*
|
|
119
|
+
* O índice sai do que ELE declarou, pelo contrato do framework - ver `idiom-names.ts`.
|
|
120
|
+
*/
|
|
121
|
+
for (const [spelling, name] of utilities)
|
|
122
|
+
if (mentions(text, spelling))
|
|
123
|
+
found.add(name);
|
|
134
124
|
return [...found];
|
|
135
125
|
}
|
|
136
126
|
/** A regra escreve este nome - por inteiro, ou pela folha dele. */
|
package/package.json
CHANGED