synthesisui 0.16.198 → 0.16.200

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.
@@ -172,6 +172,29 @@ async function sheetWithImports(path, seen = new Set(), depth = 0) {
172
172
  async function exists(path) {
173
173
  return await readFile(path, "utf8").then(() => true, () => false);
174
174
  }
175
+ /**
176
+ * SOB QUE NOME O ESCOPO É IMPORTADO - a única forma de reconhecer a linha da própria biblioteca.
177
+ *
178
+ * O app dele importa `@frontend-hub/ui`; o `packages/ui/package.json` declara esse nome. É a mesma
179
+ * regra que a lei 13 já usa para desempatar `workspace:` contra semver, um passo antes: o MANIFESTO
180
+ * responde, e não o nome do componente nem o formato do caminho.
181
+ *
182
+ * Ausente quando o escopo não é um pacote - e aí a evidência só é somada quando o nome chega de UMA
183
+ * origem interna, que é a mesma resposta por outro caminho.
184
+ */
185
+ async function publishedAs(root) {
186
+ const raw = await readFile(join(root, "package.json"), "utf8").catch(() => null);
187
+ if (!raw)
188
+ return null;
189
+ try {
190
+ const name = JSON.parse(raw).name;
191
+ return typeof name === "string" && name ? name : null;
192
+ }
193
+ catch {
194
+ /** Um manifesto ilegível custa a evidência daquele escopo, nunca a medição. */
195
+ return null;
196
+ }
197
+ }
175
198
  async function harvestOwnCss(root,
176
199
  /**
177
200
  * O LEDGER TAMBÉM COME AQUI, e por um motivo de promessa: este é o único laço que vê TODO
@@ -1065,7 +1088,18 @@ export async function takeCensus(root, opts) {
1065
1088
  */
1066
1089
  const turnedAway = new Set(skips.map((s) => s.name));
1067
1090
  const inventory = allComposed.filter((c) => !turnedAway.has(c.name));
1068
- const composed = new Set(inventory.map((c) => c.name));
1091
+ /**
1092
+ * COMPOSTO POR ELE, e não por qualquer um que use esse nome - a mesma lei, o terceiro sítio.
1093
+ *
1094
+ * Este conjunto responde "preciso criar uma linha a partir da DECLARAÇÃO, ou já existe uma linha
1095
+ * de uso?". Com uma linha por nome, "existe" era inequívoco. Com uma linha por nome+origem, um
1096
+ * `Toggle` do `@base-ui/react` respondia que sim - e o `Toggle` que ele declara em
1097
+ * `packages/ui/atoms` ficava SEM LINHA NENHUMA, que é o mesmo desaparecimento do test50 chegando
1098
+ * por outra porta.
1099
+ *
1100
+ * A linha de terceiro não responde por ele: só conta como composto o que veio de dentro.
1101
+ */
1102
+ const composed = new Set(inventory.filter((c) => !c.from).map((c) => c.name));
1069
1103
  const fromTypes = defined
1070
1104
  .filter((d) => !composed.has(d.name))
1071
1105
  .map((d) => ({
@@ -1160,8 +1194,64 @@ export async function takeCensus(root, opts) {
1160
1194
  * the laws read, and it is the reason this pass exists: three files agreeing is
1161
1195
  * what makes a habit, and a library has one file per component.
1162
1196
  */
1163
- const own = new Set(scoped.map((c) => c.name));
1164
- const evidence = new Map(usedThere.filter((c) => !c.from).map((c) => [c.name, c]));
1197
+ /**
1198
+ * A ORIGEM DECIDE, E AUSÊNCIA DE ORIGEM NÃO É UMA ORIGEM - lei 13, no segundo sítio.
1199
+ *
1200
+ * `tallyToInventory` já separa as linhas por nome+origem, e o censo mostra isso funcionando em 8
1201
+ * nomes (`Tooltip`, `Popover.*`, `AnimatePresence`, `Toggle`). Aqui a evidência voltava a casar por
1202
+ * NOME, e o filtro era `!c.from` - que junta três coisas diferentes numa só: o que é dele, o que
1203
+ * tem origem misturada, e o que não tem origem NENHUMA.
1204
+ *
1205
+ * O que isso produziu, medido no repo do dono em 10/08: o `Button` DELE publicava
1206
+ * `variant = ["ocean", "outlined", "contained"]`. `outlined` e `contained` são do MUI, e os
1207
+ * `declaredAxes` dele não têm nenhum dos dois - as opções que ele declara são `ocean`, `royal`,
1208
+ * `ai-outline`, `danger` e mais cinco. Os oito arquivos que importam o Button do MUI já eram
1209
+ * separados pelo `from`; o vazamento veio de um arquivo com 25 usos de `<Button>` e NENHUM import
1210
+ * de `Button` - origem desconhecida, tratada como dele.
1211
+ *
1212
+ * E "DE DENTRO" NÃO ERA UMA ORIGEM, era uma vizinhança: `OWN` valia para todo caminho interno,
1213
+ * então o `Button` da biblioteca dele e o `Button` do Chakra re-exportado atrás do alias `@/` eram
1214
+ * a MESMA linha. Exigir só `own` trocava o vazamento de terceiro por um vazamento de vizinho, e
1215
+ * pior: seis leis - `enlarge`, `on`, `hover`, `click`, `asChild`, `onlyBorder` - escritas sobre um
1216
+ * componente dele que não tem nenhuma dessas props. A origem agora é o especificador inteiro, e a
1217
+ * evidência casa com a linha que veio da PRÓPRIA biblioteca.
1218
+ *
1219
+ * Sem conseguir dizer QUAL linha é a da biblioteca, ninguém soma nada: a direção conservadora é a
1220
+ * mesma dos dois lados - deixar de somar evidência custa uma contagem, somar a errada custa uma
1221
+ * LEI escrita sobre a biblioteca de outra pessoa.
1222
+ */
1223
+ /** Os nomes que a biblioteca declara - a pergunta aqui é de PERTENCIMENTO, não de origem. */
1224
+ const inScope = new Set(scoped.map((c) => c.name));
1225
+ /** `root` aqui já É a pasta do escopo - `runImport` resolve `--scope` antes de chamar. */
1226
+ const published = await publishedAs(root);
1227
+ const evidence = new Map();
1228
+ /** Os nomes que chegam de mais de um lugar de dentro - a lacuna, contada para ser dita. */
1229
+ const ambiguous = [];
1230
+ const byName = new Map();
1231
+ for (const c of usedThere) {
1232
+ if (!c.own)
1233
+ continue;
1234
+ byName.set(c.name, [...(byName.get(c.name) ?? []), c]);
1235
+ }
1236
+ for (const [name, rows] of byName) {
1237
+ /**
1238
+ * A LINHA DA BIBLIOTECA, quando dá para nomeá-la: o app importa o pacote que o escopo publica.
1239
+ * É a resposta exata, e ela não depende de resolver caminho nenhum.
1240
+ */
1241
+ const mine = published
1242
+ ? rows.filter((r) => r.origin === published || r.origin?.startsWith(`${published}/`))
1243
+ : [];
1244
+ if (mine.length === 1) {
1245
+ evidence.set(name, mine[0]);
1246
+ continue;
1247
+ }
1248
+ /** Sem pacote para casar, uma origem interna só também é resposta: não há o que confundir. */
1249
+ if (mine.length === 0 && rows.length === 1) {
1250
+ evidence.set(name, rows[0]);
1251
+ continue;
1252
+ }
1253
+ ambiguous.push(name);
1254
+ }
1165
1255
  const merged = scoped.map((c) => {
1166
1256
  const e = evidence.get(c.name);
1167
1257
  if (!e)
@@ -1198,7 +1288,15 @@ export async function takeCensus(root, opts) {
1198
1288
  : {}),
1199
1289
  };
1200
1290
  });
1201
- const theirsOnly = usedThere.filter((c) => !c.from && !own.has(c.name));
1291
+ /**
1292
+ * O QUE O APP TEM E A BIBLIOTECA NÃO - e a mesma lei: só entra o que tem origem DELE.
1293
+ *
1294
+ * A frase que sai daqui diz "estes são componentes seus que vivem só no app". Sem `own`, um nome
1295
+ * de origem desconhecida entrava na lista, e a tela oferecia ao cliente um componente que pode ser
1296
+ * de terceiro - a mesma acusação que o `<Button variant="contained">` sem import produzia do
1297
+ * outro lado.
1298
+ */
1299
+ const theirsOnly = usedThere.filter((c) => c.own && !inScope.has(c.name));
1202
1300
  // The verdict travels WITH the payload: the platform reads one reading rather
1203
1301
  // than computing a second opinion from the same numbers, which is how two
1204
1302
  // implementations of the same judgement start disagreeing.
@@ -1211,8 +1309,19 @@ export async function takeCensus(root, opts) {
1211
1309
  * itself. See `isLibrary`.
1212
1310
  */
1213
1311
  const library = isLibrary(scoped);
1312
+ /**
1313
+ * O VEREDITO É DE UMA LINHA, NÃO DE UM NOME - o quarto sítio da mesma lei.
1314
+ *
1315
+ * `crosswalk` já julga linha a linha e pula quem tem `from`: o `Tooltip` do `recharts` sai dele
1316
+ * SEM veredito, que é o certo. Mas o mapa era por NOME, então o veredito do `Tooltip` DELE -
1317
+ * `exclusive`, um componente que ele escreveu - era colado também na linha do recharts. Medido em
1318
+ * 10/08: `recharts` e `@base-ui/react` chegavam ao censo como componentes exclusivos dele.
1319
+ *
1320
+ * A chave é nome+origem, que é a mesma identidade que o censo passou a carregar.
1321
+ */
1322
+ const idOf = (c) => `${c.name}\u0000${c.origin ?? ""}`;
1214
1323
  const verdicts = new Map(crosswalk(merged, { library }).map((r) => [
1215
- r.component.name,
1324
+ idOf(r.component),
1216
1325
  { bucket: r.bucket, canonical: r.canonical, because: r.because },
1217
1326
  ]));
1218
1327
  if (usedThere.length > 0) {
@@ -1515,7 +1624,7 @@ export async function takeCensus(root, opts) {
1515
1624
  }
1516
1625
  const components = merged.map((c) => ({
1517
1626
  ...c,
1518
- ...(verdicts.get(c.name) ?? {}),
1627
+ ...(verdicts.get(idOf(c)) ?? {}),
1519
1628
  ...(laws.has(c.name) ? { laws: laws.get(c.name) } : {}),
1520
1629
  ...(runtimeOf.has(c.name) ? { runtime: runtimeOf.get(c.name) } : {}),
1521
1630
  ...(companionsOf.has(c.name)
@@ -2341,10 +2450,31 @@ function sayReach(c) {
2341
2450
  * shadow one of their own names. `declared` is a DEFINITION of theirs, which is what makes
2342
2451
  * this a tie-break rather than a preference.
2343
2452
  */
2453
+ /**
2454
+ * O DESEMPATE QUE DEIXOU DE SER NECESSÁRIO - e que virou uma reivindicação falsa.
2455
+ *
2456
+ * Ele nasceu quando uma linha era um NOME: o `Pill` deles fazia
2457
+ * `import { Toggle } from "@base-ui/react/toggle"`, e esse nome colidia com o `Toggle` que eles
2458
+ * escreveram uma pasta ao lado - a linha saía carregando `from: "@base-ui/react/toggle"`, e `from` é
2459
+ * o que faz o crosswalk pular um componente. O Toggle DELES sumia do sistema inteiro (test50,
2460
+ * 04/08). Apagar o `from` era a única saída, porque não havia duas linhas para escolher.
2461
+ *
2462
+ * Agora há: a origem entra na chave, então o `Toggle` deles e o do `@base-ui/react` são duas linhas.
2463
+ * Continuar apagando o `from` da linha de terceiro faz a esteira reivindicar o componente ALHEIO -
2464
+ * medido em 10/08 no repo do dono: o `Tooltip` do `recharts` e o `Toggle` do `@base-ui/react`
2465
+ * chegavam ao censo como `exclusive`, ou seja, como componentes que ele teria escrito. É exatamente
2466
+ * o caso que a lei 13 nomeia.
2467
+ *
2468
+ * A regra passa a olhar a ORIGEM: um `from` só se apaga quando não há origem para distinguir as
2469
+ * duas - o mundo antigo, e o único em que o desempate ainda é honesto.
2470
+ */
2344
2471
  export function declaredWins(component, declared) {
2345
- return component.from && declared
2346
- ? { ...component, from: undefined }
2347
- : component;
2472
+ if (!component.from || !declared)
2473
+ return component;
2474
+ /** Com origem, a linha dele existe à parte e esta aqui é de quem a exportou. */
2475
+ if (component.origin)
2476
+ return component;
2477
+ return { ...component, from: undefined };
2348
2478
  }
2349
2479
  function rootPackage(tag, sketch) {
2350
2480
  if (!tag || !/^[A-Z]/.test(tag))
@@ -2,14 +2,146 @@ import { readdir, readFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  /**
4
4
  * Opens a JSX element whose name is capitalised - which is React's own rule for
5
- * "this is a component, not an html tag".
5
+ * "this is a component, not an html tag". The name allows dots (`Card.Header`).
6
6
  *
7
- * The name allows dots (`Card.Header`) and the body is captured lazily up to
8
- * the first `>` that is not inside a quoted value.
7
+ * SÓ A ABERTURA. O corpo NÃO vem daqui - ver `bodyOf`.
9
8
  */
10
- const ELEMENT = /<([A-Z][A-Za-z0-9_]*(?:\.[A-Z][A-Za-z0-9_]*)*)(\s[^>]*?)?\/?>/gs;
11
- /** `variant="primary"` or `variant={"primary"}` - a literal a person typed. */
12
- const LITERAL_PROP = /([a-zA-Z][a-zA-Z0-9_-]*)\s*=\s*\{?\s*["']([^"']*)["']\s*\}?/g;
9
+ const OPENS = /<([A-Z][A-Za-z0-9_]*(?:\.[A-Z][A-Za-z0-9_]*)*)/g;
10
+ /**
11
+ * ONDE A TAG TERMINA DE VERDADE - e isto era uma varredura de caractere.
12
+ *
13
+ * O corpo era `(\s[^>]*?)?`: para no primeiro `>` que aparecer, sem saber o que é aspa e
14
+ * sem saber o que é aninhamento. Medido no repositório do dono em 10/08, isso produzia
15
+ * DOIS defeitos que pareciam não ter relação:
16
+ *
17
+ * <WidgetCard rightTitleSlot={ <Button variant="neutral" onClick={() => …
18
+ * os atributos do FILHO caem no corpo do PAI - o censo registrou `variant: neutral`
19
+ * no WidgetCard, que declara `default | left-border | full-border`. A tela reportou
20
+ * "uso fora do contrato" sobre um uso que nunca existiu.
21
+ *
22
+ * value="… Domain 'sg.futurescope.com' … the 'Sending' tab …"
23
+ * a aspa simples dentro da dupla fechava o valor cedo, o resto da frase ficava no
24
+ * corpo, e o passe de booleano nu colhia palavra por palavra: `the`, `following`,
25
+ * `registrar`, `domains`. Dezenove palavras viraram props do Textarea.
26
+ *
27
+ * Um defeito INVENTADO é pior que um não achado: o não achado custa uma lacuna, o
28
+ * inventado custa a confiança na tela inteira. Por isso a resposta não é acrescentar
29
+ * casos à regex - é um scanner que sabe o que está lendo, e que fecha por construção.
30
+ *
31
+ * Devolve o corpo e onde continuar a varredura. Uma tag sem fechamento (arquivo cortado,
32
+ * TypeScript genérico que não era elemento) devolve `null` e não vira uso.
33
+ */
34
+ function bodyOf(source, from) {
35
+ let quote = null;
36
+ let depth = 0;
37
+ for (let i = from; i < source.length; i++) {
38
+ const ch = source[i];
39
+ if (quote) {
40
+ /** Escape dentro de string: `\"` não fecha nada. */
41
+ if (ch === "\\")
42
+ i += 1;
43
+ else if (ch === quote)
44
+ quote = null;
45
+ continue;
46
+ }
47
+ if (ch === '"' || ch === "'" || ch === "`") {
48
+ quote = ch;
49
+ continue;
50
+ }
51
+ if (ch === "{")
52
+ depth += 1;
53
+ else if (ch === "}")
54
+ depth -= 1;
55
+ /** Em profundidade zero e fora de aspas: aqui a tag realmente fecha. */ else if (ch === ">" &&
56
+ depth <= 0)
57
+ return {
58
+ body: withoutExpressions(source.slice(from, i)).replace(/\/$/, ""),
59
+ end: i + 1,
60
+ };
61
+ }
62
+ return null;
63
+ }
64
+ /**
65
+ * O QUE ESTÁ DENTRO DE CHAVES NÃO É PROP DESTA TAG - e sem isto o corpo termina no lugar certo
66
+ * e ainda entrega o filho errado.
67
+ *
68
+ * `<WidgetCard rightTitleSlot={<Button variant="neutral" />} />` fecha corretamente no `/>` do
69
+ * WidgetCard: o `>` do Button está em profundidade 1. Mas o corpo continua carregando
70
+ * `variant="neutral"`, porque a atribuição do filho mora dentro da expressão do slot.
71
+ *
72
+ * Então a expressão vira espaço - e o que ela contém não se perde: o `<Button` dentro dela é
73
+ * encontrado pela própria varredura, no lugar dele, com as props dele.
74
+ *
75
+ * COM UMA EXCEÇÃO, E ELA É MEDIDA: `variant={"info"}` é o MESMO literal que `variant="info"`, e um
76
+ * spec já dizia isso. A primeira versão deste passe apagava os dois e a suíte reprovou - eu tinha
77
+ * escrito no comentário que o `LITERAL_PROP` via o corpo cru antes, o que o código não fazia. Uma
78
+ * chave contendo só um literal volta como valor; qualquer outra coisa dentro é expressão.
79
+ */
80
+ function withoutExpressions(body) {
81
+ let out = "";
82
+ let inner = "";
83
+ let depth = 0;
84
+ let quote = null;
85
+ /** Fecha uma expressão: um literal puro é VALOR e volta; o resto é do que estiver dentro. */
86
+ const close = () => {
87
+ out += /^\s*(["'`])(?:\\.|(?!\1)[^\\])*\1\s*$/.test(inner)
88
+ ? `{${inner}}`
89
+ : " ";
90
+ inner = "";
91
+ };
92
+ for (let i = 0; i < body.length; i++) {
93
+ const ch = body[i];
94
+ const keep = (text) => {
95
+ if (depth === 0)
96
+ out += text;
97
+ else
98
+ inner += text;
99
+ };
100
+ if (quote) {
101
+ if (ch === "\\") {
102
+ keep(body.slice(i, i + 2));
103
+ i += 1;
104
+ }
105
+ else {
106
+ keep(ch);
107
+ if (ch === quote)
108
+ quote = null;
109
+ }
110
+ continue;
111
+ }
112
+ if (ch === '"' || ch === "'" || ch === "`") {
113
+ quote = ch;
114
+ keep(ch);
115
+ continue;
116
+ }
117
+ if (ch === "{") {
118
+ depth += 1;
119
+ if (depth > 1)
120
+ inner += ch;
121
+ continue;
122
+ }
123
+ if (ch === "}") {
124
+ depth -= 1;
125
+ if (depth === 0)
126
+ close();
127
+ else if (depth > 0)
128
+ inner += ch;
129
+ else
130
+ depth = 0;
131
+ continue;
132
+ }
133
+ keep(ch);
134
+ }
135
+ return out;
136
+ }
137
+ /**
138
+ * `variant="primary"` ou `variant={'primary'}` - um literal que alguém digitou.
139
+ *
140
+ * A ASPA CASA COM ELA MESMA (`(["'])…\2`), e não com qualquer uma. `["']([^"']*)["']`
141
+ * tratava as duas como intercambiáveis, então um valor com apóstrofo dentro truncava - e o
142
+ * que sobrava virava prop. Ver `bodyOf`.
143
+ */
144
+ const LITERAL_PROP = /([a-zA-Z][a-zA-Z0-9_-]*)\s*=\s*\{?\s*(["'])((?:\\.|(?!\2)[^\\])*)\2\s*\}?/g;
13
145
  /** `dense` with no value is a boolean prop set to true, and that IS a literal
14
146
  * decision worth counting. */
15
147
  const BARE_PROP = /(^|\s)([a-z][a-zA-Z0-9_]*)(?=\s|$)/g;
@@ -103,8 +235,19 @@ function importedNames(source, internal = []) {
103
235
  if (!part || part === "*")
104
236
  continue;
105
237
  const local = (part.split(/\s+as\s+/).pop() ?? "").trim();
238
+ /**
239
+ * O ESPECIFICADOR VIAJA MESMO QUANDO É DE DENTRO - e `own` vira uma resposta à parte.
240
+ *
241
+ * `OWN` era um balde só para TODO caminho interno, então o `Button` da biblioteca dele e o
242
+ * `Button` do Chakra re-exportado atrás do alias `@/` eram a mesma linha. Medido no repo do
243
+ * dono em 10/08: `Button` chega de 10 alvos internos diferentes, 6 deles dentro dos apps, e a
244
+ * união deles escreveu seis leis - `enlarge`, `on`, `hover`, `click`, `asChild`, `onlyBorder` -
245
+ * sobre um componente que não tem nenhuma dessas props.
246
+ *
247
+ * A lei 13 diz que a origem decide. "De dentro" não é uma origem, é uma vizinhança.
248
+ */
106
249
  if (/^[A-Z]/.test(local))
107
- out.set(local, own ? OWN : spec);
250
+ out.set(local, { spec, own });
108
251
  }
109
252
  }
110
253
  return out;
@@ -171,8 +314,12 @@ internal = []) {
171
314
  return;
172
315
  const owners = importedNames(source, internal);
173
316
  const private_ = localOnlyNames(source);
174
- ELEMENT.lastIndex = 0;
175
- for (const m of source.matchAll(ELEMENT)) {
317
+ /**
318
+ * Abertura por regex, corpo por SCANNER - ver `bodyOf`. A regex só encontra `<Nome`; onde a
319
+ * tag termina é uma pergunta que só um scanner com estado responde.
320
+ */
321
+ OPENS.lastIndex = 0;
322
+ for (const m of source.matchAll(OPENS)) {
176
323
  if (m.index != null && looksLikeType(source, m.index))
177
324
  continue;
178
325
  const name = m[1];
@@ -180,31 +327,50 @@ internal = []) {
180
327
  // shadow nothing and still bind a name it also imports.
181
328
  if (!owners.has(name) && private_.has(name.split(".")[0]))
182
329
  continue;
183
- const body = m[2] ?? "";
330
+ const read = bodyOf(source, (m.index ?? 0) + m[0].length);
331
+ /** Tag sem fechamento não é uso: arquivo cortado, ou um genérico que não era elemento. */
332
+ if (!read)
333
+ continue;
334
+ const body = read.body;
184
335
  // A dotted name belongs to whoever exported its root (`Popover.Root`).
185
336
  const owner = owners.get(name) ?? owners.get(name.split(".")[0]);
186
- /** Ver `ComponentTally`: dois `Button` de pacotes diferentes são duas linhas, não uma. */
187
- let hit = tally.get(name);
337
+ /**
338
+ * A ORIGEM ENTRA NA CHAVE - a lei 13 do CLAUDE.md, que este comentário já afirmava e o código
339
+ * não cumpria: a chave era só o nome.
340
+ *
341
+ * Medido no repositório do dono em 10/08: os apps usam o `Button` DELE e o do MUI, e as
342
+ * variantes do MUI (`contained`, `outlined`, `text`) caíam na conta do Button dele, que declara
343
+ * `ocean`, `royal`, `neutral`. O mesmo com `CircularProgress` e `determinate`. E era a mesma
344
+ * causa do `own: true` valer em 7 de 128: uma linha que mistura duas origens nunca tem TODAS
345
+ * as origens de dentro.
346
+ */
347
+ const key = `${name}\u0000${owner?.spec ?? ""}`;
348
+ let hit = tally.get(key);
188
349
  if (!hit) {
189
350
  hit = {
190
351
  count: 0,
191
352
  files: new Set(),
192
353
  props: new Map(),
193
354
  propFiles: new Map(),
194
- ...(owner && owner !== OWN ? { from: owner } : {}),
355
+ ...(owner && !owner.own ? { from: owner.spec } : {}),
195
356
  origins: new Set(),
196
357
  };
197
- tally.set(name, hit);
358
+ tally.set(key, hit);
198
359
  }
199
- /** TODA origem vista, e não só a primeira - ver `origins` no tipo. */
360
+ /**
361
+ * A ORIGEM AGORA ESTÁ INTEIRA NA CHAVE, então esta linha tem uma só. `origins` continua
362
+ * existindo porque é ele que separa "veio de um lugar que eu sei ler" de "não veio de lugar
363
+ * nenhum" - e é a segunda que a lei 13 manda não atribuir a ninguém.
364
+ */
200
365
  if (owner)
201
- hit.origins.add(owner);
366
+ hit.origins.add(owner.own ? OWN : owner.spec);
202
367
  hit.count += 1;
203
368
  hit.files.add(file);
204
369
  LITERAL_PROP.lastIndex = 0;
205
370
  const named = new Set();
206
371
  for (const p of body.matchAll(LITERAL_PROP)) {
207
- const [, prop, value] = p;
372
+ /** O grupo 2 é a ASPA (a backreference); o valor é o 3 - ver `LITERAL_PROP`. */
373
+ const [, prop, , value] = p;
208
374
  named.add(prop);
209
375
  if (NOT_A_DECISION.test(prop))
210
376
  continue;
@@ -239,8 +405,17 @@ internal = []) {
239
405
  /** The inventory, commonest first. */
240
406
  export function tallyToInventory(tally, max = 80) {
241
407
  return [...tally.entries()]
242
- .map(([name, v]) => ({
243
- name,
408
+ .map(([key, v]) => ({
409
+ /** A chave carrega a origem para separar as linhas - o nome é a primeira metade dela. */
410
+ name: key.split("\u0000")[0],
411
+ /**
412
+ * E A SEGUNDA METADE VIAJA, que é o que permite a quem lê perguntar QUAL. Sem isto o censo
413
+ * separava as linhas e devolvia só o nome - foi exatamente assim que a evidência de uso voltou
414
+ * a casar por NOME um passo depois desta função, em `runImport`.
415
+ */
416
+ ...(key.slice(key.indexOf("\u0000") + 1)
417
+ ? { origin: key.slice(key.indexOf("\u0000") + 1) }
418
+ : {}),
244
419
  ...(v.from ? { from: v.from } : {}),
245
420
  /** Só dele quando TODA aparição veio de dentro - ver `origins` e `OWN`. */
246
421
  ...(v.origins.size > 0 && [...v.origins].every((o) => o === OWN)
@@ -60,8 +60,36 @@ export function checkContracts(used, documents) {
60
60
  }
61
61
  if (axes.size === 0)
62
62
  return [];
63
+ /**
64
+ * E O NOME QUE CHEGA DE MAIS DE UM LUGAR DE DENTRO NÃO SE COBRA DE NINGUÉM.
65
+ *
66
+ * A terceira forma da mesma lei, e a que faltava. `own` responde "veio de dentro do projeto", que
67
+ * é uma VIZINHANÇA e não uma origem: um `Button` do Chakra re-exportado atrás do alias `@/` é tão
68
+ * "de dentro" quanto o da biblioteca dele.
69
+ *
70
+ * Medido no repo do dono em 10/08, no `apps/web-dashboard`: `Button` chega de DEZ lugares, cinco
71
+ * deles de dentro - `@frontend-hub/ui` (34 arquivos), `@/components/ui/_legacy/_chakra-ui/button`
72
+ * (25), `@/components/ui/_legacy/_general/Button` (19), `@/components/ui/Button` (3) e
73
+ * `../Button` (1). A ÚNICA acusação que o doctor fazia contra o sistema dele era
74
+ * `variant="outline"`, que é do Chakra: as cinco variantes que o Button DELE recebe estão todas
75
+ * entre as nove que ele declara.
76
+ *
77
+ * Uma acusação falsa custa mais que uma acusação perdida: ela ensina a pessoa a não acreditar na
78
+ * próxima. Enquanto a identidade não se divide de verdade, o mínimo honesto é a esteira SABER que
79
+ * é ambíguo e calar sobre esse nome.
80
+ */
81
+ const insideOrigins = new Map();
82
+ for (const c of used) {
83
+ if (!c.own)
84
+ continue;
85
+ const seen = insideOrigins.get(c.name) ?? new Set();
86
+ seen.add(c.origin ?? "");
87
+ insideOrigins.set(c.name, seen);
88
+ }
63
89
  const out = [];
64
90
  for (const c of used) {
91
+ if ((insideOrigins.get(c.name)?.size ?? 0) > 1)
92
+ continue;
65
93
  // A third party's component answers to its own package, not to this system.
66
94
  if (c.from)
67
95
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.198",
3
+ "version": "0.16.200",
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": {