synthesisui 0.16.263 → 0.16.265
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 +131 -10
- package/dist/commands/connect.js +16 -0
- package/dist/commands/mcp.js +106 -0
- package/dist/commands/sync.js +73 -0
- package/dist/install-marks.js +16 -1
- package/dist/memory/availability.js +71 -0
- package/dist/memory/contract.js +106 -0
- package/dist/memory/observation.js +77 -0
- package/dist/memory/predicate.js +243 -0
- package/dist/memory/recall.js +161 -0
- package/dist/memory/report.js +31 -0
- package/dist/memory/snapshot.js +47 -0
- package/dist/memory/tools.js +315 -0
- package/dist/memory/work-state.js +131 -0
- package/dist/memory/write.js +139 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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,111 @@ 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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
+
/**
|
|
402
|
+
* O RECALL SÓ É PROMETIDO ONDE ELE TEM ONDE CHEGAR - e este era um GAP de jornada.
|
|
403
|
+
*
|
|
404
|
+
* `recallAvailable` responde "o servidor MCP está registrado numa versão que serve `recall`", e essa
|
|
405
|
+
* é a pergunta certa sobre CAPACIDADE. Ela não é a pergunta sobre ALCANCE: `memoryReach` no servidor
|
|
406
|
+
* exige `installedSlug`, que ignora locks `adopted` porque um sistema adotado é lido do repositório e
|
|
407
|
+
* não tem linha de dashboard para a memória pertencer.
|
|
408
|
+
*
|
|
409
|
+
* Sem esta condição, um projeto só-adotado recebia um bootstrap dizendo *"what this project already
|
|
410
|
+
* DECIDED is `recall`"* e toda chamada respondia "no system installed here that belongs to a
|
|
411
|
+
* dashboard row" - o bootstrap prometendo uma ferramenta que não podia funcionar ali. As outras do
|
|
412
|
+
* bloco reduzido continuam valendo, porque `system_doctrine` e `find_token` leem arquivo local.
|
|
413
|
+
*
|
|
414
|
+
* Achado seguindo a jornada, não lendo o módulo: os dois lados estavam certos sobre a própria
|
|
415
|
+
* pergunta e ninguém comparava as duas.
|
|
416
|
+
*/
|
|
417
|
+
const memoryReachable = recall.available && !onlyAdopted;
|
|
418
|
+
const memoryLine = memoryReachable
|
|
419
|
+
? `
|
|
420
|
+
|
|
421
|
+
**What this project already DECIDED is \`recall\`.** Call it when you begin work on an indexed
|
|
422
|
+
subject, or when someone says "continue" and you need the current work-state. For the same subject
|
|
423
|
+
and task, call it once - an empty result means there is nothing more to retrieve, so keep working. It
|
|
424
|
+
tells you what was decided and why it matters; **the file on disk is still the authority for what is
|
|
425
|
+
written now** - read the code before you edit it, never a remembered signature.`
|
|
426
|
+
: "";
|
|
427
|
+
const rule = recall.available
|
|
428
|
+
? onlyAdopted
|
|
429
|
+
? `**These are true without asking anyone:** use the project's OWN custom properties, exactly as
|
|
430
|
+
this system declares them. No raw colours, spacings or radii that a token already covers, and never a
|
|
431
|
+
new token invented in silence - say so instead, because a new token is a decision for a person. There
|
|
432
|
+
is no component index for an adopted system: the tokens ARE the contract.
|
|
433
|
+
|
|
434
|
+
**What the vocabulary IS is not written here.** Fetch only the piece the task requires:
|
|
435
|
+
\`system_doctrine\` for the rules and the voice, \`find_token\` for a value you are about to write.${memoryLine}${selfCheck}`
|
|
436
|
+
: `**These are true without asking anyone:**
|
|
437
|
+
|
|
438
|
+
- Semantic tokens only - \`var(--ds-color-semantic-*)\`, \`--ds-spacing-*\`. No raw values outside this
|
|
439
|
+
system's scale.
|
|
440
|
+
- Scope the UI with \`data-ds="<slug>"\` and reuse the \`.ds-*\` classes.
|
|
441
|
+
- To override a style a component already sets, use this system's semantic role class - never \`!\`.
|
|
442
|
+
If the override is ignored, regenerate that component: older ones predate the resolver.
|
|
443
|
+
- Motion is selection, not improvisation. The base is quiet: nothing moves until a person asks, and
|
|
444
|
+
what moves comes from this system's own vocabulary - never hand-rolled \`@keyframes\` or raw durations.
|
|
445
|
+
|
|
446
|
+
**When you are writing UI that belongs to one of the systems below, check its index first.** If an
|
|
447
|
+
entry covers the purpose, do not write it from scratch - materialise it:
|
|
448
|
+
|
|
449
|
+
npx synthesisui@latest component <slug> <name>
|
|
450
|
+
|
|
451
|
+
If nothing covers it, say which indexed entry you considered and why it did not fit, then file a
|
|
452
|
+
\`request_component\` while the reasoning is still fresh. A refusal said only in chat evaporates.
|
|
453
|
+
|
|
454
|
+
**What an entry IS is not written here.** Fetch only the piece the task requires:
|
|
455
|
+
|
|
456
|
+
describe_component its parts, variants, states and the tokens it already uses
|
|
457
|
+
system_doctrine the rules and the voice of this system
|
|
458
|
+
playbook the families, and what each one requires before it can be drawn${memoryLine}${selfCheck}`
|
|
459
|
+
: onlyAdopted
|
|
460
|
+
? `**When creating or editing components, read the system's GUIDE.md and follow it:** use the
|
|
340
461
|
project's OWN custom properties, exactly as the guide lists them. Do not write raw colours,
|
|
341
462
|
spacings or radii that a token already covers, and do not invent a new token silently - say so
|
|
342
463
|
instead, because a new token is a decision for a person to make. There is no component
|
|
343
464
|
manifest for an adopted system - the tokens ARE the contract.${selfCheck}`
|
|
344
|
-
|
|
465
|
+
: `**When creating or editing components, read the system's GUIDE.md and follow it:** use only semantic tokens
|
|
345
466
|
(\`var(--ds-color-semantic-*)\`, \`--ds-spacing-*\`, etc.), scope the UI with \`data-ds="<slug>"\`,
|
|
346
467
|
and reuse the \`.ds-*\` classes. Do not use raw values outside the system's scale.
|
|
347
468
|
|
package/dist/commands/connect.js
CHANGED
|
@@ -11,6 +11,7 @@ import { add } from "./add.js";
|
|
|
11
11
|
import { reportWhatIsLeft } from "./align.js";
|
|
12
12
|
import { ci } from "./ci.js";
|
|
13
13
|
import { MCP_TOOL_COUNT } from "./mcp.js";
|
|
14
|
+
import { installedSlugs } from "./sync.js";
|
|
14
15
|
/**
|
|
15
16
|
* `synthesisui connect` - put the three layers where they actually run.
|
|
16
17
|
*
|
|
@@ -213,6 +214,21 @@ export async function connect(opts) {
|
|
|
213
214
|
// after them, not before.
|
|
214
215
|
const contract = await syncClaudeMd(root);
|
|
215
216
|
console.log(section("Connected"));
|
|
217
|
+
/**
|
|
218
|
+
* E O QUE FALTA, DITO EM VOZ ALTA - porque este comando estava mentindo por omissão.
|
|
219
|
+
*
|
|
220
|
+
* `connect` liga a conta e registra o servidor MCP. Ele NÃO instala sistema, e sem lock o bloco
|
|
221
|
+
* gerenciado do `CLAUDE.md` sai VAZIO: o agente não recebe índice, não recebe invariante, e as duas
|
|
222
|
+
* ferramentas de memória recusam com "no system installed here". A saída dizia apenas "Connected",
|
|
223
|
+
* que é verdade e é inútil.
|
|
224
|
+
*
|
|
225
|
+
* Custou tempo de gente de verdade: 20/08, `codelevel-monorepo`. Rodou `connect`, conferiu o
|
|
226
|
+
* `CLAUDE.md`, achou o bloco vazio, e não havia nada dizendo que faltava um comando. Lacuna
|
|
227
|
+
* silenciosa é o que faz um produto correto parecer quebrado.
|
|
228
|
+
*/
|
|
229
|
+
const anyInstalled = (await installedSlugs(root).catch(() => [])).length > 0;
|
|
230
|
+
if (!anyInstalled)
|
|
231
|
+
console.log(body("· no design system installed here yet - `synthesisui list` shows yours, and `synthesisui add <slug>` brings one in. Until then the agent has no index and memory has nothing to belong to."));
|
|
216
232
|
/**
|
|
217
233
|
* O QUE ESTE CLI REESCREVEU NA PASTA DO SISTEMA - dito primeiro, porque é o que a pessoa não sabia
|
|
218
234
|
* que estava devendo. Ela rodou `connect` para atualizar a fiação; os arquivos do install estarem
|
package/dist/commands/mcp.js
CHANGED
|
@@ -9,7 +9,10 @@ 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 { fromCensus } from "../memory/observation.js";
|
|
13
|
+
import { handleRecall, handleRemember, MEMORY_TOOLS } from "../memory/tools.js";
|
|
12
14
|
import { repoStateOf } from "../repo-state.js";
|
|
15
|
+
import { detectStack } from "../stack.js";
|
|
13
16
|
import { component } from "./component.js";
|
|
14
17
|
import { loadSystem, walkAll } from "./doctor.js";
|
|
15
18
|
import { installedSlug, remeasure } from "./sync.js";
|
|
@@ -272,6 +275,14 @@ const TOOLS = [
|
|
|
272
275
|
required: ["name", "purpose"],
|
|
273
276
|
},
|
|
274
277
|
},
|
|
278
|
+
/**
|
|
279
|
+
* A MEMÓRIA - o que este projeto já decidiu, e o que alguém está fazendo agora.
|
|
280
|
+
*
|
|
281
|
+
* Duas ferramentas estreitas em vez de uma genérica: quanto menos combinatória o schema oferece,
|
|
282
|
+
* menos estado impossível existe para validar depois. Os dois schemas são fechados e NENHUM campo
|
|
283
|
+
* derivado aparece neles - `memory/tools.spec.ts` percorre os dois e reprova se um vazar.
|
|
284
|
+
*/
|
|
285
|
+
...MEMORY_TOOLS,
|
|
275
286
|
];
|
|
276
287
|
/**
|
|
277
288
|
* QUANTAS FERRAMENTAS ESTE SERVIDOR SERVE, lido da lista.
|
|
@@ -1238,6 +1249,39 @@ cli) {
|
|
|
1238
1249
|
* procedência. Nenhuma linha dele conta nada: quem instrumenta é `callTool`,
|
|
1239
1250
|
* uma vez, para todas.
|
|
1240
1251
|
*/
|
|
1252
|
+
/**
|
|
1253
|
+
* O ELO QUE FALTAVA - `remember` e `recall` eram ANUNCIADOS e não despachados.
|
|
1254
|
+
*
|
|
1255
|
+
* As duas estavam na lista de `TOOLS` desde o primeiro dia, importadas no topo deste arquivo, com 152
|
|
1256
|
+
* testes verdes atrás delas - e sem `case` no `runTool`. Um agente que as chamasse recebia
|
|
1257
|
+
* `"unknown tool"` com `isError`. O `reachable.spec` não pegou porque IMPORTAR já conta como
|
|
1258
|
+
* alcançável, e o próprio cabeçalho dele diz o que ele não cobre: *"nothing about whether the product
|
|
1259
|
+
* calls it, and that gap is exactly where the work kept"*.
|
|
1260
|
+
*
|
|
1261
|
+
* Achado seguindo a jornada pelo ponto de entrada real, que é a lei 3. Um spec verde prova que a
|
|
1262
|
+
* função roda; não prova que o produto a chama.
|
|
1263
|
+
*/
|
|
1264
|
+
async function memoryReach(root) {
|
|
1265
|
+
const slug = await installedSlug(root);
|
|
1266
|
+
if (!slug)
|
|
1267
|
+
return "No system installed here that belongs to a dashboard row, so there is nothing to remember about. `synthesisui add <slug>` installs one.";
|
|
1268
|
+
const token = await readToken();
|
|
1269
|
+
if (!token)
|
|
1270
|
+
return "Not signed in, so memory cannot be reached. A person has to run `synthesisui login` once in the terminal - it opens a browser, and I cannot complete it for them.";
|
|
1271
|
+
return { slug, token, base: resolveRegistry() };
|
|
1272
|
+
}
|
|
1273
|
+
/** O censo já em disco, como Observation - o mesmo insumo que o `sync` mede. */
|
|
1274
|
+
async function localObservation(root) {
|
|
1275
|
+
const raw = await readFile(join(root, "_synthesisui", "census.json"), "utf8").catch(() => null);
|
|
1276
|
+
if (!raw)
|
|
1277
|
+
return null;
|
|
1278
|
+
try {
|
|
1279
|
+
return fromCensus(JSON.parse(raw));
|
|
1280
|
+
}
|
|
1281
|
+
catch {
|
|
1282
|
+
return null;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1241
1285
|
async function runTool(root, name, args) {
|
|
1242
1286
|
switch (name) {
|
|
1243
1287
|
case "check_file":
|
|
@@ -1397,6 +1441,68 @@ async function runTool(root, name, args) {
|
|
|
1397
1441
|
? `${said}\n\nUPDATE THE DESIGN SYSTEM BEFORE CONTINUING.\nThe check that runs after your writes is pinned to ${behind} and these tools are ${VERSION}.\n\n npx synthesisui@latest connect\n\nAsk the person, run it for them, and then say this: close and reopen the editor. The hook and the tools only rewire on a new session, so nothing you write before that is checked by the reader you are holding.`
|
|
1398
1442
|
: said);
|
|
1399
1443
|
}
|
|
1444
|
+
case "remember": {
|
|
1445
|
+
const reach = await memoryReach(root);
|
|
1446
|
+
if (typeof reach === "string")
|
|
1447
|
+
return fromContract(reach);
|
|
1448
|
+
/**
|
|
1449
|
+
* A OBSERVAÇÃO É EXIGIDA para work-state, e só para ele: a condição de saída tem que ser
|
|
1450
|
+
* verificável contra ESTE repositório, senão a memória nasce impossível de fechar.
|
|
1451
|
+
*/
|
|
1452
|
+
const obs = await localObservation(root);
|
|
1453
|
+
if (!obs)
|
|
1454
|
+
return fromContract("No `_synthesisui/census.json` here, so nothing can be checked against this repository. Run `npx synthesisui sync` first - a work state whose condition nobody can measure is memory that starts lying.");
|
|
1455
|
+
return handleRemember(args, obs, async (payload) => {
|
|
1456
|
+
const res = await fetch(`${reach.base}/api/memory/${reach.slug}/remember`, {
|
|
1457
|
+
method: "POST",
|
|
1458
|
+
headers: {
|
|
1459
|
+
"content-type": "application/json",
|
|
1460
|
+
Authorization: `Bearer ${reach.token}`,
|
|
1461
|
+
},
|
|
1462
|
+
body: JSON.stringify(payload),
|
|
1463
|
+
}).catch(() => null);
|
|
1464
|
+
if (!res)
|
|
1465
|
+
return {
|
|
1466
|
+
accepted: false,
|
|
1467
|
+
reason: "unknown_field",
|
|
1468
|
+
details: "the platform could not be reached",
|
|
1469
|
+
};
|
|
1470
|
+
const said = (await res.json().catch(() => null));
|
|
1471
|
+
return (said ?? {
|
|
1472
|
+
accepted: false,
|
|
1473
|
+
reason: "unknown_field",
|
|
1474
|
+
details: `the platform answered ${res.status} with nothing readable`,
|
|
1475
|
+
});
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
case "recall": {
|
|
1479
|
+
const reach = await memoryReach(root);
|
|
1480
|
+
if (typeof reach === "string")
|
|
1481
|
+
return fromContract(reach);
|
|
1482
|
+
/** A stack sai do repositório, nunca do agente: ela FILTRA, e um palpite dele cortaria regra boa. */
|
|
1483
|
+
const stack = await detectStack(root);
|
|
1484
|
+
return handleRecall(args, stack, async (request) => {
|
|
1485
|
+
const res = await fetch(`${reach.base}/api/memory/${reach.slug}/recall`, {
|
|
1486
|
+
method: "POST",
|
|
1487
|
+
headers: {
|
|
1488
|
+
"content-type": "application/json",
|
|
1489
|
+
Authorization: `Bearer ${reach.token}`,
|
|
1490
|
+
},
|
|
1491
|
+
body: JSON.stringify(request),
|
|
1492
|
+
}).catch(() => null);
|
|
1493
|
+
const empty = {
|
|
1494
|
+
carried: [],
|
|
1495
|
+
groups: [],
|
|
1496
|
+
omitted: [],
|
|
1497
|
+
because: res
|
|
1498
|
+
? `the platform answered ${res.status}`
|
|
1499
|
+
: "the platform could not be reached",
|
|
1500
|
+
};
|
|
1501
|
+
if (!res?.ok)
|
|
1502
|
+
return empty;
|
|
1503
|
+
return ((await res.json().catch(() => null)) ?? empty);
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1400
1506
|
case "request_token": {
|
|
1401
1507
|
const name = String(args.name ?? "");
|
|
1402
1508
|
// TRIAGE AT FILING: if the system's own contract already promises this
|
package/dist/commands/sync.js
CHANGED
|
@@ -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
|
}
|
package/dist/install-marks.js
CHANGED
|
@@ -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
|
-
|
|
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.265";
|
|
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
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O CONTRATO DA MEMÓRIA - e é a ÚNICA coisa desta feature que existe nos dois lados.
|
|
3
|
+
*
|
|
4
|
+
* O CLI é publicado standalone e não pode importar os contratos da plataforma, então este arquivo é
|
|
5
|
+
* um GÊMEO BYTE-IDÊNTICO de `apps/web/src/lib/memory/contract.ts`, garantido por spec - o mesmo
|
|
6
|
+
* padrão de `frontier-kind.ts`.
|
|
7
|
+
*
|
|
8
|
+
* A REGRA QUE DECIDE O QUE ENTRA AQUI (dono, 20/08):
|
|
9
|
+
*
|
|
10
|
+
* contrato pode ser duplicado e verificado; LÓGICA nunca.
|
|
11
|
+
*
|
|
12
|
+
* Entram tipos, enums, códigos de recusa e a metadata declarativa dos predicados. NÃO entram
|
|
13
|
+
* `evaluate`, `compareProgress`, `canonicalize`, `recall` nem qualquer função que decida algo: cada
|
|
14
|
+
* uma dessas tem um dono só, e duplicar semântica é como duas implementações começam a divergir sem
|
|
15
|
+
* ninguém perceber.
|
|
16
|
+
*
|
|
17
|
+
* E a divisão que este arquivo serve:
|
|
18
|
+
*
|
|
19
|
+
* O CLI OBSERVA E TESTEMUNHA. A PLATAFORMA LEMBRA E DECIDE O EFEITO DA EVIDÊNCIA.
|
|
20
|
+
*/
|
|
21
|
+
export const CATALOGUE = {
|
|
22
|
+
reference_replaced: {
|
|
23
|
+
needs: ["from", "to"],
|
|
24
|
+
requiresAlive: ["subject"],
|
|
25
|
+
requiresReadableStyles: false,
|
|
26
|
+
},
|
|
27
|
+
inbound_references_zero: {
|
|
28
|
+
needs: [],
|
|
29
|
+
requiresAlive: [],
|
|
30
|
+
requiresReadableStyles: false,
|
|
31
|
+
progress: { field: "remaining", direction: "decrease" },
|
|
32
|
+
},
|
|
33
|
+
reference_present: {
|
|
34
|
+
needs: ["to"],
|
|
35
|
+
requiresAlive: ["subject"],
|
|
36
|
+
requiresReadableStyles: false,
|
|
37
|
+
},
|
|
38
|
+
reference_absent: {
|
|
39
|
+
needs: ["from"],
|
|
40
|
+
requiresAlive: ["subject"],
|
|
41
|
+
requiresReadableStyles: false,
|
|
42
|
+
},
|
|
43
|
+
token_present: {
|
|
44
|
+
needs: ["to"],
|
|
45
|
+
requiresAlive: [],
|
|
46
|
+
requiresReadableStyles: true,
|
|
47
|
+
},
|
|
48
|
+
token_absent: {
|
|
49
|
+
needs: ["from"],
|
|
50
|
+
requiresAlive: [],
|
|
51
|
+
requiresReadableStyles: true,
|
|
52
|
+
},
|
|
53
|
+
component_present: {
|
|
54
|
+
needs: [],
|
|
55
|
+
requiresAlive: [],
|
|
56
|
+
requiresReadableStyles: false,
|
|
57
|
+
},
|
|
58
|
+
component_absent: {
|
|
59
|
+
needs: [],
|
|
60
|
+
requiresAlive: [],
|
|
61
|
+
requiresReadableStyles: false,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
export const DECLARABLE = [
|
|
65
|
+
"developer_declared",
|
|
66
|
+
"agent_inferred",
|
|
67
|
+
"imported",
|
|
68
|
+
];
|
|
69
|
+
/**
|
|
70
|
+
* AS TAREFAS, LISTA FECHADA - derivada do que a plataforma JÁ MEDE.
|
|
71
|
+
*
|
|
72
|
+
* Mesma regra do catálogo: a linguagem cresce quando a capacidade de medição cresce.
|
|
73
|
+
*/
|
|
74
|
+
export const TASKS = [
|
|
75
|
+
"tokens",
|
|
76
|
+
"spacing",
|
|
77
|
+
"typography",
|
|
78
|
+
"contrast",
|
|
79
|
+
"accessibility",
|
|
80
|
+
"migration",
|
|
81
|
+
];
|
|
82
|
+
/**
|
|
83
|
+
* O QUE O AGENTE NÃO PODE PROPOR: "ele pode propor fatos, nunca a autoridade dos fatos".
|
|
84
|
+
*
|
|
85
|
+
* `verdict` e `snapshotId` entraram nesta lista quando a fronteira de medição passou a existir: um
|
|
86
|
+
* agente que pudesse enviar `verdict: "met"` se concederia a autoridade de ter medido.
|
|
87
|
+
*/
|
|
88
|
+
export const DERIVED_FIELDS = [
|
|
89
|
+
"owner",
|
|
90
|
+
"fingerprint",
|
|
91
|
+
"confidence",
|
|
92
|
+
"authority",
|
|
93
|
+
"rung",
|
|
94
|
+
"status",
|
|
95
|
+
"measuredAt",
|
|
96
|
+
"measured_at",
|
|
97
|
+
"lastProgressAt",
|
|
98
|
+
"lastMeasuredAt",
|
|
99
|
+
"evidenceSource",
|
|
100
|
+
"resolution",
|
|
101
|
+
"active",
|
|
102
|
+
"verdict",
|
|
103
|
+
"progress",
|
|
104
|
+
"snapshotId",
|
|
105
|
+
"snapshot_id",
|
|
106
|
+
];
|