synthesisui 0.16.261 → 0.16.264

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 CHANGED
@@ -2,6 +2,7 @@ import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import { hasHook } from "./agent-wiring.js";
4
4
  import { declaredReference } from "./group-role.js";
5
+ import { recallAvailable } from "./memory/availability.js";
5
6
  const START = "<!-- synthesisui:start -->";
6
7
  const END = "<!-- synthesisui:end -->";
7
8
  /** Reads the installed DSs from the .lock files in _synthesisui/ds/<slug>/. */
@@ -61,7 +62,9 @@ function catalogNames(recipes) {
61
62
  * HAS - "use these before creating new ones" - instead of re-inventing
62
63
  * buttons. Returns null when the document isn't readable (older installs).
63
64
  */
64
- async function readManifest(projectRoot, ds) {
65
+ async function readManifest(projectRoot, ds,
66
+ /** Ver `recallAvailable`: com o servidor registrado, o índice vira mapa em vez de lista. */
67
+ recall) {
65
68
  try {
66
69
  const raw = await readFile(join(projectRoot, "_synthesisui", "ds", ds.slug, `v${ds.version}`, "design-system.json"), "utf8");
67
70
  const doc = JSON.parse(raw);
@@ -71,14 +74,29 @@ async function readManifest(projectRoot, ds) {
71
74
  return null;
72
75
  const lines = [];
73
76
  if (components.length > 0) {
74
- lines.push(` Components (${components.length}) - look here BEFORE writing anything new:`);
77
+ /**
78
+ * A LEGENDA MUDA COM A CAPACIDADE, e os NOMES nunca.
79
+ *
80
+ * Com `recall` ao alcance, o índice deixa de ser "a lista do que existe" e passa a ser o mapa de
81
+ * onde perguntar - é essa a diferença entre carregar conhecimento e ensinar a encontrá-lo. Os
82
+ * nomes saem da MESMA fonte canônica nos dois casos (`design-system.json`), na mesma ordem, sem
83
+ * um item a mais nem a menos: o F0 troca uma frase, não o inventário.
84
+ */
85
+ lines.push(recall
86
+ ? ` Components (${components.length}) - the index; each name has knowledge you can retrieve:`
87
+ : ` Components (${components.length}) - look here BEFORE writing anything new:`);
75
88
  lines.push(` ${components.map((n) => `ds-${n}`).join(" ")}`);
76
89
  }
77
90
  if (blocks.length > 0) {
78
91
  lines.push(` Engagement blocks (${blocks.length}):`);
79
92
  lines.push(` ${blocks.map((n) => `ds-${n}`).join(" ")}`);
80
93
  }
81
- lines.push(" What each one does, its variants and states: the GUIDE above.");
94
+ /**
95
+ * E A LINHA QUE MANDAVA LER O GUIA SAI quando há como perguntar. Ela custava 24.647 bytes de
96
+ * leitura no sistema medido - a mesma informação que `describe_component` serve por componente.
97
+ */
98
+ if (!recall)
99
+ lines.push(" What each one does, its variants and states: the GUIDE above.");
82
100
  return lines.join("\n");
83
101
  }
84
102
  catch {
@@ -297,6 +315,14 @@ async function renderRegion(projectRoot, installed) {
297
315
  if (installed.length === 0) {
298
316
  return `${START}\n${END}`;
299
317
  }
318
+ /**
319
+ * A CAPACIDADE, MEDIDA UMA VEZ POR ARQUIVO - ver `recallAvailable`.
320
+ *
321
+ * Não é "qual agente é este": é "este repositório tem o servidor registrado, numa versão que serve
322
+ * `recall`?". Os dois editores gravam o mesmo shape em caminhos diferentes, então a checagem vale
323
+ * para qualquer agente que leia esse formato - inclusive um que ainda não existe.
324
+ */
325
+ const recall = await recallAvailable(projectRoot);
300
326
  const sections = [];
301
327
  for (const ds of installed) {
302
328
  // An adopted system has no published version and therefore no `v<n>/`
@@ -305,7 +331,7 @@ async function renderRegion(projectRoot, installed) {
305
331
  const head = ds.adopted
306
332
  ? `- **${ds.name}** (\`${ds.slug}\`, adopted from this repo) - guide: \`_synthesisui/ds/${ds.slug}/GUIDE.md\``
307
333
  : `- **${ds.name}** (\`${ds.slug}\`, v${ds.version}) - guide: \`_synthesisui/ds/${ds.slug}/v${ds.version}/GUIDE.md\``;
308
- const manifest = await readManifest(projectRoot, ds);
334
+ const manifest = await readManifest(projectRoot, ds, recall.available);
309
335
  sections.push(manifest ? `${head}\n${manifest}` : head);
310
336
  }
311
337
  // Two truths, and asserting the wrong one misleads the agent every time it
@@ -332,16 +358,97 @@ async function renderRegion(projectRoot, installed) {
332
358
  sections.push(`\n**${ref.name}** (\`${ref.slug}\`) is the reference system of this group. When two of the systems above name the same thing differently, its answer is the one to follow - the others are moving towards it.`);
333
359
  }
334
360
  const onlyAdopted = installed.every((d) => d.adopted);
335
- const selfCheck = (await hasHook(projectRoot))
336
- ? SELF_CHECK_HOOKED
337
- : SELF_CHECK_MANUAL;
338
- const rule = onlyAdopted
339
- ? `**When creating or editing components, read the system's GUIDE.md and follow it:** use the
361
+ /**
362
+ * O PORTÃO LOCAL OBRIGATÓRIO, EM DUAS LINHAS quando há como buscar o resto.
363
+ *
364
+ * O texto longo explica três ferramentas - o `doctor --verbose`, o `request_token` e o
365
+ * `refresh_system` -, com a justificativa de cada uma. Justificativa é conhecimento recuperável:
366
+ * ela mora no `doctor` e no playbook, que é onde a pessoa e o agente estão quando ela importa.
367
+ *
368
+ * O que NÃO é recuperável e por isso fica: que existe um portão, que ele roda por arquivo tocado, e
369
+ * que um valor sem token não se inventa. É gate obrigatório, não documentação.
370
+ */
371
+ const selfCheck = recall.available
372
+ ? `
373
+
374
+ **Check what you wrote** with \`check_file\` on the file you touched, and fix what it names before
375
+ moving on. If it names a value this system has no token for, do not invent one - file it the same way
376
+ you would file a missing component. And call \`refresh_system\` once per session before writing UI
377
+ here: a newer reader sees styles the old one could not.`
378
+ : (await hasHook(projectRoot))
379
+ ? SELF_CHECK_HOOKED
380
+ : SELF_CHECK_MANUAL;
381
+ /**
382
+ * O BOOTSTRAP É REDUZIDO QUANDO EXISTE CAMINHO DE VOLTA - e o portão é CAPACIDADE, não nome.
383
+ *
384
+ * Bootstrap contains invariants, discovery, and mandatory local gates;
385
+ * retrievable knowledge stays outside the bootstrap.
386
+ *
387
+ * Sem servidor MCP registrado, o texto longo continua inteiro: cortar a instrução de ler o guia
388
+ * onde não há ferramenta para perguntar trocaria contexto por ignorância. `add` escreve este
389
+ * arquivo e `connect` registra o servidor - são dois comandos, então a garantia não vem por
390
+ * construção e tem que ser medida.
391
+ *
392
+ * O que SAI daqui: "read the system's GUIDE.md and follow it" (24.647 bytes no sistema medido) e a
393
+ * linha que mandava o guia explicar cada componente. O que FICA: as invariantes que ninguém
394
+ * descobre sozinho, o índice, a materialização, os nomes das três ferramentas de descoberta com uma
395
+ * linha cada, o `recall` por assunto, e o portão local obrigatório (`check_file`).
396
+ *
397
+ * A instrução de `connect` NÃO entra na versão reduzida: o mesmo `wireAgent` que registra o
398
+ * servidor instala o hook, e o registro é pré-condição para este texto existir. Ensinar a instalar
399
+ * uma capacidade que foi pré-requisito do próprio arquivo é redundância por construção.
400
+ */
401
+ const rule = recall.available
402
+ ? onlyAdopted
403
+ ? `**These are true without asking anyone:** use the project's OWN custom properties, exactly as
404
+ this system declares them. No raw colours, spacings or radii that a token already covers, and never a
405
+ new token invented in silence - say so instead, because a new token is a decision for a person. There
406
+ is no component index for an adopted system: the tokens ARE the contract.
407
+
408
+ **What the vocabulary IS is not written here.** Fetch only the piece the task requires:
409
+ \`system_doctrine\` for the rules and the voice, \`find_token\` for a value you are about to write.
410
+
411
+ **What this project already DECIDED is \`recall\`.** Call it when you begin work on a subject this
412
+ system names, or when someone says "continue" and you need the current work-state. For the same
413
+ subject and task, call it once - an empty result means there is nothing more to retrieve, so keep
414
+ working. It tells you what was decided and why it matters; **the file on disk is still the authority
415
+ for what is written now** - read the code before you edit it, never a remembered signature.${selfCheck}`
416
+ : `**These are true without asking anyone:**
417
+
418
+ - Semantic tokens only - \`var(--ds-color-semantic-*)\`, \`--ds-spacing-*\`. No raw values outside this
419
+ system's scale.
420
+ - Scope the UI with \`data-ds="<slug>"\` and reuse the \`.ds-*\` classes.
421
+ - To override a style a component already sets, use this system's semantic role class - never \`!\`.
422
+ If the override is ignored, regenerate that component: older ones predate the resolver.
423
+ - Motion is selection, not improvisation. The base is quiet: nothing moves until a person asks, and
424
+ what moves comes from this system's own vocabulary - never hand-rolled \`@keyframes\` or raw durations.
425
+
426
+ **When you are writing UI that belongs to one of the systems below, check its index first.** If an
427
+ entry covers the purpose, do not write it from scratch - materialise it:
428
+
429
+ npx synthesisui@latest component <slug> <name>
430
+
431
+ If nothing covers it, say which indexed entry you considered and why it did not fit, then file a
432
+ \`request_component\` while the reasoning is still fresh. A refusal said only in chat evaporates.
433
+
434
+ **What an entry IS is not written here.** Fetch only the piece the task requires:
435
+
436
+ describe_component its parts, variants, states and the tokens it already uses
437
+ system_doctrine the rules and the voice of this system
438
+ playbook the families, and what each one requires before it can be drawn
439
+
440
+ **What this project already DECIDED is \`recall\`.** Call it when you begin work on an indexed
441
+ subject, or when someone says "continue" and you need the current work-state. For the same subject
442
+ and task, call it once - an empty result means there is nothing more to retrieve, so keep working. It
443
+ tells you what was decided and why it matters; **the file on disk is still the authority for what is
444
+ written now** - read the code before you edit it, never a remembered signature.${selfCheck}`
445
+ : onlyAdopted
446
+ ? `**When creating or editing components, read the system's GUIDE.md and follow it:** use the
340
447
  project's OWN custom properties, exactly as the guide lists them. Do not write raw colours,
341
448
  spacings or radii that a token already covers, and do not invent a new token silently - say so
342
449
  instead, because a new token is a decision for a person to make. There is no component
343
450
  manifest for an adopted system - the tokens ARE the contract.${selfCheck}`
344
- : `**When creating or editing components, read the system's GUIDE.md and follow it:** use only semantic tokens
451
+ : `**When creating or editing components, read the system's GUIDE.md and follow it:** use only semantic tokens
345
452
  (\`var(--ds-color-semantic-*)\`, \`--ds-spacing-*\`, etc.), scope the UI with \`data-ds="<slug>"\`,
346
453
  and reuse the \`.ds-*\` classes. Do not use raw values outside the system's scale.
347
454
 
@@ -4,7 +4,7 @@ import { anatomyFromSketch } from "../anatomy-from-sketch.js";
4
4
  import { resolveAnatomy, resolveFlatParts, safePartName, } from "../anatomy-read.js";
5
5
  import { readCredentials, readToken, resolveRegistry, sameRegistry, } from "../config.js";
6
6
  import { declaredElsewhere } from "../declared-elsewhere.js";
7
- import { architectureRule, describeArchitecture, describeChoice, detectArchitectures, } from "../doctor/architecture.js";
7
+ import { architectureRule, componentHome, describeArchitecture, describeChoice, detectArchitectures, homeLine, proposeNewHome, } from "../doctor/architecture.js";
8
8
  import { findBrokenRefs } from "../doctor/broken-refs.js";
9
9
  import { nestingRules, propRules, readDefinitionProps, readNesting, readRuntime, } from "../doctor/call-sites.js";
10
10
  import { asCatalogueTable, describeFallback, fetchCatalogue, } from "../doctor/catalogue-fetch.js";
@@ -37,6 +37,7 @@ import { frontierKind, packageRoot } from "../frontier-kind.js";
37
37
  import { withLibraryStructure } from "../library-structure.js";
38
38
  import { mergeCensus } from "../merge-census.js";
39
39
  import { claimName } from "../name-claim.js";
40
+ import { namingQueue } from "../naming-queue.js";
40
41
  import { body, paint, section } from "../output.js";
41
42
  import { phase, startProgress } from "../progress.js";
42
43
  import { detectStack, resolveDeps, stackVersions } from "../stack.js";
@@ -1928,6 +1929,18 @@ export async function takeCensus(root, opts) {
1928
1929
  const brokenRefs = findBrokenRefs(sources, declaredNames);
1929
1930
  const conventions = detectConventions(sources);
1930
1931
  const classStyle = detectClassStyle(sources);
1932
+ /**
1933
+ * A FILA DE NOMES, computada com os MESMOS insumos que o envio usa - ver `namingQueue`.
1934
+ *
1935
+ * O crosswalk e o manifesto entram porque é o que distingue um componente DELES de uma peça de
1936
+ * terceiro, e os dois casos são nós já nomeados. Sem eles a fila cobraria trabalho que não
1937
+ * existe, que é exatamente o defeito que ela vem medir.
1938
+ */
1939
+ const naming = namingQueue(looks, (name) => {
1940
+ const c = components.find((x) => x.name === name && !x.from);
1941
+ const target = c?.canonical ?? (c?.bucket === "exclusive" ? c.name : null);
1942
+ return target ? safePartName(target) : null;
1943
+ }, (pkg) => versions[pkg]);
1931
1944
  return {
1932
1945
  census: 1,
1933
1946
  project: {
@@ -1964,6 +1977,7 @@ export async function takeCensus(root, opts) {
1964
1977
  ...(conventions.length > 0 ? { conventions } : {}),
1965
1978
  classStyle,
1966
1979
  ...(Object.keys(looks).length > 0 ? { looks } : {}),
1980
+ ...(naming.components > 0 ? { naming } : {}),
1967
1981
  ...(schemes.alt.size > 0
1968
1982
  ? { declaredAlt: Object.fromEntries(schemes.alt) }
1969
1983
  : {}),
@@ -2026,8 +2040,16 @@ export async function takeCensus(root, opts) {
2026
2040
  ? {
2027
2041
  architectures: architectures.slice(0, 6).map((a) => ({
2028
2042
  ...a,
2029
- rule: architectureRule(a),
2043
+ /** O caminho e a frase que a PERGUNTA usa verbatim - ver `componentHome`. */
2044
+ home: componentHome(a, scopeLabel),
2045
+ line: homeLine(a),
2046
+ rule: architectureRule(a, scopeLabel),
2030
2047
  })),
2048
+ ...(proposeNewHome(architectures, scopeLabel)
2049
+ ? {
2050
+ newComponentHome: proposeNewHome(architectures, scopeLabel),
2051
+ }
2052
+ : {}),
2031
2053
  }
2032
2054
  : {}),
2033
2055
  ...(skips.length > 0
@@ -3570,7 +3592,11 @@ export async function runImport(opts) {
3570
3592
  "content-type": "application/json",
3571
3593
  Authorization: `Bearer ${token}`,
3572
3594
  },
3573
- body: JSON.stringify({ census, name: chosen }),
3595
+ body: JSON.stringify({
3596
+ census,
3597
+ name: chosen,
3598
+ ...(opts.group ? { group: opts.group } : {}),
3599
+ }),
3574
3600
  }).catch(() => null);
3575
3601
  if (!res || !res.ok) {
3576
3602
  const detail = res
@@ -3602,6 +3628,15 @@ export async function runImport(opts) {
3602
3628
  console.log("");
3603
3629
  console.log(section("Your system exists"));
3604
3630
  console.log(body(`${paint.strong(payload?.name ?? "Your system")} - v1 mirrors your tokens exactly, nothing improved yet.`));
3631
+ /**
3632
+ * ONDE ELE NASCEU, dito pela plataforma e não pelo que foi pedido.
3633
+ *
3634
+ * A flag carrega um nome falado e o casamento acontece do outro lado, então imprimir o que foi
3635
+ * pedido provaria nada. Este é o campo que o dono não teve em 20/08, quando dois sistemas
3636
+ * nasceram no espaço pessoal dele e o grupo ficou vazio.
3637
+ */
3638
+ if (payload?.group)
3639
+ console.log(body(paint.dim(`In the group ${payload.group}.`)));
3605
3640
  for (const note of payload?.notes ?? [])
3606
3641
  console.log(body(paint.dim(note)));
3607
3642
  // What a v2 would be worth, in their own values. Said here because this is
@@ -9,6 +9,7 @@ import { readEvents } from "../doctor/ledger.js";
9
9
  import { fileRequest } from "../doctor/requests.js";
10
10
  import { diagnose, nameToWrite, scanSource } from "../doctor/scan.js";
11
11
  import { nearestToken, normalizeValue, tokenFor } from "../doctor/tokens.js";
12
+ import { MEMORY_TOOLS } from "../memory/tools.js";
12
13
  import { repoStateOf } from "../repo-state.js";
13
14
  import { component } from "./component.js";
14
15
  import { loadSystem, walkAll } from "./doctor.js";
@@ -272,6 +273,14 @@ const TOOLS = [
272
273
  required: ["name", "purpose"],
273
274
  },
274
275
  },
276
+ /**
277
+ * A MEMÓRIA - o que este projeto já decidiu, e o que alguém está fazendo agora.
278
+ *
279
+ * Duas ferramentas estreitas em vez de uma genérica: quanto menos combinatória o schema oferece,
280
+ * menos estado impossível existe para validar depois. Os dois schemas são fechados e NENHUM campo
281
+ * derivado aparece neles - `memory/tools.spec.ts` percorre os dois e reprova se um vazar.
282
+ */
283
+ ...MEMORY_TOOLS,
275
284
  ];
276
285
  /**
277
286
  * QUANTAS FERRAMENTAS ESTE SERVIDOR SERVE, lido da lista.
@@ -6,6 +6,8 @@ import { markSent, readEvents } from "../doctor/ledger.js";
6
6
  import { checkableName, closeRequest, readRequests, verifyAndCloseRequests, } from "../doctor/requests.js";
7
7
  import { describeDelta, fingerprintReadings, readSyncMark, writeSyncMark, } from "../last-sync.js";
8
8
  import { measuredScope, rememberScope } from "../measured-scope.js";
9
+ import { fromCensus } from "../memory/observation.js";
10
+ import { reportMeasurement } from "../memory/report.js";
9
11
  import { mergeCensus } from "../merge-census.js";
10
12
  import { body, paint, section, snippet } from "../output.js";
11
13
  import { repoStateOf } from "../repo-state.js";
@@ -506,6 +508,77 @@ export async function remeasure(args) {
506
508
  };
507
509
  console.log(body(paint.faint(describeDelta(mark, await readSyncMark(root)))));
508
510
  await writeSyncMark(root, mark);
511
+ /**
512
+ * O CANAL DE MEDIÇÃO - o `sync` testemunha o que a plataforma não pode observar.
513
+ *
514
+ * Ela guarda os trabalhos declarados ("estamos migrando o Checkout para o novo Button"); o
515
+ * repositório está aqui. Então este comando pergunta o que está aberto, mede tudo com um snapshot
516
+ * só, e devolve os veredictos - e é por isso que o trabalho fecha sozinho quando a realidade o
517
+ * fecha, em vez de esperar alguém lembrar.
518
+ *
519
+ * DEGRADAÇÃO SEGURA, e ela não é acidental: uma plataforma que ainda não tem a rota - o intervalo
520
+ * normal entre publicar no npm e fazer deploy - responde 404, o `open()` devolve nada, e o `sync`
521
+ * segue exatamente como antes. Um repositório sem trabalho declarado também não paga nada.
522
+ */
523
+ const work = await reportMeasurement({
524
+ at: new Date().toISOString(),
525
+ ...(args.cli ? { cli: args.cli } : {}),
526
+ ...(scope ? { scope } : {}),
527
+ obs: fromCensus(census),
528
+ open: async () => {
529
+ /**
530
+ * SEM REDE NÃO É "FEATURE AUSENTE", e a diferença é fina mas importa (dono, 20/08).
531
+ *
532
+ * Aqui ela fica precisa por um detalhe deste comando: o `sync` já falou com a plataforma no
533
+ * começo - ele buscou a leitura guardada e teria parado se não tivesse resposta. Então uma
534
+ * exceção de rede NESTE ponto é anômala, não é o modo offline: a plataforma respondia trinta
535
+ * segundos atrás. Ela vira aviso, e um 404 continua sendo silêncio de compatibilidade.
536
+ */
537
+ let res = null;
538
+ try {
539
+ res = await fetch(`${base}/api/memory/${slug}/open`, {
540
+ headers: { Authorization: `Bearer ${token}` },
541
+ });
542
+ }
543
+ catch (e) {
544
+ return {
545
+ ok: false,
546
+ reason: "error",
547
+ detail: `network: ${e instanceof Error ? e.message : String(e)}`,
548
+ };
549
+ }
550
+ /** 404 é a rota que ainda não subiu - o intervalo entre publicar no npm e fazer deploy. */
551
+ if (res.status === 404)
552
+ return { ok: false, reason: "not-installed" };
553
+ if (!res.ok)
554
+ return {
555
+ ok: false,
556
+ reason: "error",
557
+ status: res.status,
558
+ };
559
+ const payload = (await res.json().catch(() => null));
560
+ return { ok: true, open: payload?.open ?? [] };
561
+ },
562
+ send: async (payload) => {
563
+ await fetch(`${base}/api/memory/${slug}/measured`, {
564
+ method: "POST",
565
+ headers: {
566
+ "content-type": "application/json",
567
+ Authorization: `Bearer ${token}`,
568
+ },
569
+ body: JSON.stringify(payload),
570
+ });
571
+ },
572
+ }).catch((e) => ({
573
+ measured: 0,
574
+ skipped: null,
575
+ failed: { detail: e instanceof Error ? e.message : String(e) },
576
+ }));
577
+ /** UM DEFEITO DE MEDIÇÃO NÃO PODE SER SILÊNCIO - é a diferença entre 404 e o resto. */
578
+ if ("failed" in work && work.failed)
579
+ console.log(body(paint.faint(`⚠ declared work could not be re-checked${work.failed.status ? ` (HTTP ${work.failed.status})` : work.failed.detail ? ` (${work.failed.detail})` : ""} - your measurement was sent, this part was not`)));
580
+ if (work.measured > 0)
581
+ console.log(body(paint.faint(`${work.measured} declared piece${work.measured === 1 ? "" : "s"} of work re-checked against this measurement`)));
509
582
  if (!args.full)
510
583
  console.log(body(paint.faint("everything the reader saw: npx synthesisui sync --full")));
511
584
  }
@@ -106,15 +106,77 @@ export function describeArchitecture(a) {
106
106
  * open one component. And `fact: true`, because it was read off their directories: it is
107
107
  * true once, not a habit that needs a third sighting.
108
108
  */
109
- export function architectureRule(a) {
109
+ export function architectureRule(a,
110
+ /**
111
+ * O ESCOPO, para a regra dizer um caminho que existe. `a.root` é relativo ao escopo, e uma
112
+ * regra no CLAUDE.md de um monorepo que diz `src/lib/SignalUI` manda o agente para um caminho
113
+ * que não resolve da raiz - o mesmo defeito que fez o dono reprovar as opções da pergunta.
114
+ */
115
+ scope) {
116
+ const home = componentHome(a, scope);
110
117
  return {
111
- text: describeArchitecture(a),
118
+ text: `${describeArchitecture(a)} A new component goes under \`${home}\`.`,
112
119
  applies: [],
113
120
  kind: "implementation",
114
121
  fact: true,
115
- evidence: `${a.evidence.join("/")} under ${a.root}, holding ${a.files} component file${a.files === 1 ? "" : "s"}`,
122
+ evidence: `${a.evidence.join("/")} under ${scope ? `${scope}/${a.root}` : a.root}, holding ${a.files} component file${a.files === 1 ? "" : "s"}`,
116
123
  };
117
124
  }
125
+ /**
126
+ * ONDE UM COMPONENTE NOVO VAI, ESCRITO COMO CAMINHO - e o caminho é a resposta.
127
+ *
128
+ * O dono leu esta pergunta na tela em 20/08 e a reprovou por uma razão que a versão
129
+ * anterior não tinha como resolver: *"'manter a escada atômica' não responde onde nasce
130
+ * o componente"*. O título de cada opção era o NOME DO PADRÃO e a árvore estava enterrada
131
+ * numa descrição de quatro linhas - então a pergunta pedia uma decisão de organização e
132
+ * mostrava um glossário.
133
+ *
134
+ * A resposta a "onde" é um caminho. Então o caminho é o título, ele vem daqui pronto, e
135
+ * ele é COMPLETO a partir da raiz do repositório: `src/lib/SignalUI` sem o `packages/ui/`
136
+ * na frente é ambíguo num monorepo, e é para o CLAUDE.md que essa linha vai.
137
+ */
138
+ export function componentHome(a, scope) {
139
+ const base = scope ? `${scope.replace(/\/+$/, "")}/${a.root}` : a.root;
140
+ const tidy = base.replace(/\/\.$/, "").replace(/^\.\//, "");
141
+ return a.evidence.length > 0 ? `${tidy}/{${a.evidence.join(", ")}}` : tidy;
142
+ }
143
+ /**
144
+ * A MESMA FORMA EM UMA FRASE - o que decide, e o número que a torna credível.
145
+ *
146
+ * `describeArchitecture` continua existindo e continua longo: ele é a REGRA que vai para
147
+ * o CLAUDE.md, onde um agente precisa do critério inteiro. Numa lista de opções esse
148
+ * mesmo texto é o que faz três linhas viraram doze e ninguém ler nenhuma.
149
+ */
150
+ const LINES = {
151
+ atomic: (a) => `A rung per what it composes - atom, molecule, organism. ${a.files} files already here.`,
152
+ feature: (a) => `Beside the feature that uses it; it moves up only when a second one needs it. ${a.files} files.`,
153
+ layered: (a) => `By layer - ui, hooks, utils - not by feature. ${a.files} files already here.`,
154
+ flat: (a) => `One folder deep, no ladder. Legible while the system is small. ${a.files} files.`,
155
+ colocated: (a) => `Beside the route that renders it, shared only when a second page needs it. ${a.files} files.`,
156
+ };
157
+ export function homeLine(a) {
158
+ return LINES[a.kind](a);
159
+ }
160
+ /**
161
+ * UMA PASTA NOVA, PROPOSTA - a opção que faltava, e o dono pediu por ela.
162
+ *
163
+ * As opções eram todas retratos: manter isto, manter aquilo. Nenhuma deixava alguém dizer
164
+ * *"daqui em diante vai num lugar novo"*, que é uma resposta legítima e frequente - e sem
165
+ * ela a pergunta é sobre o passado.
166
+ *
167
+ * O caminho é DERIVADO, não inventado: fica dentro do escopo que a pessoa já apontou como
168
+ * fonte de verdade, e usa `src/` só quando as formas medidas mostram que o escopo tem `src`.
169
+ * Sem nome de sistema dentro dele de propósito - quem quer isso digita, e digitar um nome
170
+ * é mais honesto do que a gente adivinhar a grafia dele.
171
+ */
172
+ export function proposeNewHome(found, scope) {
173
+ const base = scope ? scope.replace(/\/+$/, "") : "";
174
+ const hasSrc = found.some((a) => a.root === "src" || a.root.startsWith("src/"));
175
+ const trunk = `${base ? `${base}/` : ""}${hasSrc ? "src/components" : "components"}`;
176
+ const fresh = `${trunk}/{atoms, molecules, organisms}`;
177
+ /** Se a proposta é o que já existe, ela não é uma pasta nova - e oferecê-la seria repetir a opção 1. */
178
+ return found.some((a) => componentHome(a, scope) === fresh) ? null : fresh;
179
+ }
118
180
  /**
119
181
  * A ESTRUTURA, ESCRITA - as pastas que a forma tem, na ordem em que ela as usa.
120
182
  *
package/dist/index.js CHANGED
@@ -88,7 +88,11 @@ Options:
88
88
  --dir <path> consumer project root (default: current directory)
89
89
  --version <n> install a specific version (default: latest)
90
90
  --ds <slug> init: bring this DS in right away · generate: target DS (default: installed)
91
- --name <name> preferred component name for generate
91
+ --name <name> import: the NAME of the system, which is what its install slug
92
+ comes from - and the slug never changes. Also the preferred
93
+ component name for generate.
94
+ --group <name> import: the GROUP it is born in, by name ("--group SignalUI").
95
+ Without it the system lands in your own space.
92
96
  --scope <path> import: the SYSTEM - tokens, components, convention. Repeatable;
93
97
  the first one wins a tie.
94
98
  --usage <path> import: the EVIDENCE - counts, chosen values, laws. Repeatable.
@@ -158,6 +162,14 @@ async function main() {
158
162
  dry: flags.dry === true,
159
163
  census: typeof flags.census === "string" ? flags.census : undefined,
160
164
  name: typeof flags.name === "string" ? flags.name : undefined,
165
+ /**
166
+ * EM QUAL GRUPO ELE NASCE - o nome, como a pessoa o fala.
167
+ *
168
+ * Sem a flag, no espaço pessoal (o padrão de sempre). Com ela, no grupo que ela nomeou -
169
+ * e um nome que a plataforma não reconhece RECUSA o envio em vez de cair no pessoal, que
170
+ * é o desvio que ninguém percebe até ir procurar o sistema.
171
+ */
172
+ group: typeof flags.group === "string" ? flags.group : undefined,
161
173
  // Only these two words. Anything else is a typo that would silently
162
174
  // invert a system, so it falls through to being measured and asked.
163
175
  // WHAT to read. `--dir` stays WHERE the project is, in every command.
@@ -100,7 +100,22 @@
100
100
  *
101
101
  * Quem instalou antes desta versão tem o stub que estanca: o `upgrade`/`connect` é o que alcança.
102
102
  */
103
- export const MATERIALISER_SINCE = "0.16.259";
103
+ /**
104
+ * `0.16.259` -> `0.16.264` em 20/08, e esta é a primeira vez nesta sessão que a marca SOBE.
105
+ *
106
+ * O F0: onde o servidor MCP está registrado, o bloco gerenciado do `CLAUDE.md` deixa de mandar ler o
107
+ * `GUIDE.md` inteiro e passa a apontar as ferramentas que servem a peça. Medido no sistema real:
108
+ * 6.815 -> 2.936 bytes de bootstrap, e a leitura de 24.647 bytes que a instrução provocava deixa de
109
+ * acontecer.
110
+ *
111
+ * UM CLIENTE PINADO RECEBE ALGO DIFERENTE NA PASTA? SIM - o texto que o agente dele lê antes de
112
+ * escrever qualquer UI é outro. É exatamente o que esta marca existe para dizer, e é por isso que ela
113
+ * anda: quem está atrás vê a versão antiga do manifesto até rodar `upgrade`.
114
+ *
115
+ * O índice de componentes é BYTE-IDÊNTICO nos dois caminhos - mesma fonte canônica, mesmos nomes,
116
+ * mesma quantidade (`claude-md.f0.spec.ts`). O corte troca uma frase, nunca o inventário.
117
+ */
118
+ export const MATERIALISER_SINCE = "0.16.264";
104
119
  /**
105
120
  * A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
106
121
  *
@@ -0,0 +1,71 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ /**
4
+ * O RECALL ESTÁ AO ALCANCE DESTE REPOSITÓRIO? - e é isto que decide o tamanho do `CLAUDE.md`.
5
+ *
6
+ * O corte do manifesto só é honesto onde existe caminho de volta. E ele NÃO é garantido por
7
+ * construção: `add` escreve o `CLAUDE.md`, `connect` registra o servidor MCP, e são dois comandos.
8
+ * Alguém que rodou `add` sem `connect` receberia um manifesto enxuto e nenhuma ferramenta para buscar
9
+ * o que saiu dele - trocaria contexto por ignorância.
10
+ *
11
+ * ─────────────────────────────────────────────────────────────────────────
12
+ * CAPACIDADE, NUNCA NOME DE AGENTE
13
+ *
14
+ * A pergunta não é "isto é Claude Code ou Cursor". É "este repositório tem o servidor registrado, numa
15
+ * versão que serve `recall`?". Os dois editores gravam o MESMO shape (`mcpServers`) em caminhos
16
+ * diferentes, então a checagem é uma só e vale para qualquer agente que leia esse formato - inclusive
17
+ * um que ainda não existe.
18
+ *
19
+ * E ela não inventa configuração: o sinal é o registro que o `connect` já escreve.
20
+ */
21
+ /** Onde os agentes leem o servidor - o mesmo shape, dois caminhos. */
22
+ const MCP_FILES = [".mcp.json", ".cursor/mcp.json"];
23
+ /** A versão em que `recall` e `remember` passaram a existir. */
24
+ export const RECALL_SINCE = "0.16.264";
25
+ const older = (mine, than) => {
26
+ const parts = (v) => v.split(".").map((p) => Number.parseInt(p, 10));
27
+ const a = parts(mine);
28
+ const b = parts(than);
29
+ for (let i = 0; i < 3; i += 1) {
30
+ if ((a[i] ?? 0) < (b[i] ?? 0))
31
+ return true;
32
+ if ((a[i] ?? 0) > (b[i] ?? 0))
33
+ return false;
34
+ }
35
+ return false;
36
+ };
37
+ export async function recallAvailable(root) {
38
+ for (const rel of MCP_FILES) {
39
+ const raw = await readFile(join(root, rel), "utf8").catch(() => null);
40
+ if (!raw)
41
+ continue;
42
+ const parsed = JSON.parse(raw);
43
+ const entry = parsed?.mcpServers?.synthesisui;
44
+ if (!entry)
45
+ continue;
46
+ /**
47
+ * UMA ENTRADA PINADA NUMA VERSÃO ANTIGA NÃO SERVE `recall`.
48
+ *
49
+ * Quem conectou antes de 06/08 ficou com o servidor pinado, e o `connect` só solta isso quando
50
+ * roda de novo. Um manifesto enxuto sobre um servidor de duas semanas atrás manda o agente chamar
51
+ * uma ferramenta que não existe naquele processo.
52
+ */
53
+ const pin = (entry.args ?? [])
54
+ .map(String)
55
+ .map((a) => /^synthesisui@(\d[\d.]*)$/.exec(a)?.[1])
56
+ .find(Boolean);
57
+ if (pin && older(pin, RECALL_SINCE))
58
+ return {
59
+ available: false,
60
+ because: `the MCP server in ${rel} is pinned to ${pin}, older than ${RECALL_SINCE} - run \`synthesisui connect\` to let it float`,
61
+ };
62
+ return {
63
+ available: true,
64
+ because: `the MCP server is registered in ${rel}`,
65
+ };
66
+ }
67
+ return {
68
+ available: false,
69
+ because: "no MCP server is registered in this repository - run `synthesisui connect` so the agent can look memory up",
70
+ };
71
+ }