synthesisui 0.16.264 → 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 +28 -14
- package/dist/commands/connect.js +16 -0
- package/dist/commands/mcp.js +98 -1
- package/dist/install-marks.js +1 -1
- package/dist/memory/tools.js +53 -7
- package/package.json +1 -1
package/dist/claude-md.js
CHANGED
|
@@ -398,6 +398,32 @@ here: a newer reader sees styles the old one could not.`
|
|
|
398
398
|
* servidor instala o hook, e o registro é pré-condição para este texto existir. Ensinar a instalar
|
|
399
399
|
* uma capacidade que foi pré-requisito do próprio arquivo é redundância por construção.
|
|
400
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
|
+
: "";
|
|
401
427
|
const rule = recall.available
|
|
402
428
|
? onlyAdopted
|
|
403
429
|
? `**These are true without asking anyone:** use the project's OWN custom properties, exactly as
|
|
@@ -406,13 +432,7 @@ new token invented in silence - say so instead, because a new token is a decisio
|
|
|
406
432
|
is no component index for an adopted system: the tokens ARE the contract.
|
|
407
433
|
|
|
408
434
|
**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}`
|
|
435
|
+
\`system_doctrine\` for the rules and the voice, \`find_token\` for a value you are about to write.${memoryLine}${selfCheck}`
|
|
416
436
|
: `**These are true without asking anyone:**
|
|
417
437
|
|
|
418
438
|
- Semantic tokens only - \`var(--ds-color-semantic-*)\`, \`--ds-spacing-*\`. No raw values outside this
|
|
@@ -435,13 +455,7 @@ If nothing covers it, say which indexed entry you considered and why it did not
|
|
|
435
455
|
|
|
436
456
|
describe_component its parts, variants, states and the tokens it already uses
|
|
437
457
|
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}`
|
|
458
|
+
playbook the families, and what each one requires before it can be drawn${memoryLine}${selfCheck}`
|
|
445
459
|
: onlyAdopted
|
|
446
460
|
? `**When creating or editing components, read the system's GUIDE.md and follow it:** use the
|
|
447
461
|
project's OWN custom properties, exactly as the guide lists them. Do not write raw colours,
|
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,8 +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 {
|
|
12
|
+
import { fromCensus } from "../memory/observation.js";
|
|
13
|
+
import { handleRecall, handleRemember, MEMORY_TOOLS } from "../memory/tools.js";
|
|
13
14
|
import { repoStateOf } from "../repo-state.js";
|
|
15
|
+
import { detectStack } from "../stack.js";
|
|
14
16
|
import { component } from "./component.js";
|
|
15
17
|
import { loadSystem, walkAll } from "./doctor.js";
|
|
16
18
|
import { installedSlug, remeasure } from "./sync.js";
|
|
@@ -1247,6 +1249,39 @@ cli) {
|
|
|
1247
1249
|
* procedência. Nenhuma linha dele conta nada: quem instrumenta é `callTool`,
|
|
1248
1250
|
* uma vez, para todas.
|
|
1249
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
|
+
}
|
|
1250
1285
|
async function runTool(root, name, args) {
|
|
1251
1286
|
switch (name) {
|
|
1252
1287
|
case "check_file":
|
|
@@ -1406,6 +1441,68 @@ async function runTool(root, name, args) {
|
|
|
1406
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.`
|
|
1407
1442
|
: said);
|
|
1408
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
|
+
}
|
|
1409
1506
|
case "request_token": {
|
|
1410
1507
|
const name = String(args.name ?? "");
|
|
1411
1508
|
// TRIAGE AT FILING: if the system's own contract already promises this
|
package/dist/install-marks.js
CHANGED
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
* O índice de componentes é BYTE-IDÊNTICO nos dois caminhos - mesma fonte canônica, mesmos nomes,
|
|
116
116
|
* mesma quantidade (`claude-md.f0.spec.ts`). O corte troca uma frase, nunca o inventário.
|
|
117
117
|
*/
|
|
118
|
-
export const MATERIALISER_SINCE = "0.16.
|
|
118
|
+
export const MATERIALISER_SINCE = "0.16.265";
|
|
119
119
|
/**
|
|
120
120
|
* A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
|
|
121
121
|
*
|
package/dist/memory/tools.js
CHANGED
|
@@ -62,7 +62,7 @@ export const MEMORY_TOOLS = [
|
|
|
62
62
|
},
|
|
63
63
|
subject: {
|
|
64
64
|
type: "string",
|
|
65
|
-
description: "What this is ABOUT - a token, a component, a library.
|
|
65
|
+
description: "decision and rationale only, and required for rationale. What this is ABOUT - a token, a component, a library. A work-state says it inside `exit.subject` instead, so sending it here is refused: a field nobody reads would look accepted.",
|
|
66
66
|
},
|
|
67
67
|
exit: {
|
|
68
68
|
type: "object",
|
|
@@ -254,16 +254,62 @@ export async function handleRecall(args, stack, ask) {
|
|
|
254
254
|
...(args.task ? { task: args.task } : {}),
|
|
255
255
|
budget,
|
|
256
256
|
});
|
|
257
|
+
/**
|
|
258
|
+
* O TRABALHO EM ABERTO VEM PRIMEIRO, e é o que responde "onde eu estava".
|
|
259
|
+
*
|
|
260
|
+
* Decisões dizem como construir; o work-state diz o que estava sendo construído. Quando alguém diz
|
|
261
|
+
* "continue", a segunda pergunta é a única que importa - e ela vinha da plataforma e era descartada
|
|
262
|
+
* aqui, porque o tipo não tinha o campo.
|
|
263
|
+
*
|
|
264
|
+
* Cada linha carrega a AUTORIDADE ao lado: um estado que ninguém mediu é hipótese, e um medido há
|
|
265
|
+
* seis dias é a última coisa que se sabe. Sem o rótulo o agente trata os dois como fato corrente.
|
|
266
|
+
*/
|
|
267
|
+
const open = (out.work ?? []).map((w) => {
|
|
268
|
+
const moved = w.remaining !== null
|
|
269
|
+
? ` ${w.remaining} left${w.moved ? ` · ${w.moved}` : ""}`
|
|
270
|
+
: w.moved
|
|
271
|
+
? ` ${w.moved}`
|
|
272
|
+
: "";
|
|
273
|
+
return `- ${w.goal} [${w.status} · ${w.says}]${moved}`;
|
|
274
|
+
});
|
|
275
|
+
const work = open.length > 0
|
|
276
|
+
? `Open work, yours:\n${open.join("\n")}\n\nWhat this project decided:\n`
|
|
277
|
+
: "";
|
|
257
278
|
if (out.carried.length === 0)
|
|
258
|
-
return
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
279
|
+
return open.length > 0
|
|
280
|
+
? answer(`Open work, yours:\n${open.join("\n")}\n\nNo decisions recorded that apply here. ${out.because}`, "memory:recall")
|
|
281
|
+
: {
|
|
282
|
+
body: `Nothing recorded that applies here. ${out.because}`,
|
|
283
|
+
source: { kind: "none", reason: "system", detail: "nothing applies" },
|
|
284
|
+
};
|
|
285
|
+
/**
|
|
286
|
+
* E O FRESCOR ATRAVESSA ATÉ AQUI - senão ele é um campo que ninguém lê.
|
|
287
|
+
*
|
|
288
|
+
* `memoriesOf` deriva `evidence` e `confirmedAt` do sinal `survived` mais recente, o único que
|
|
289
|
+
* atesta o CÓDIGO. Eles cruzavam o JSON, existiam no tipo, e esta linha renderizava só o degrau: o
|
|
290
|
+
* agente continuava sem distinguir "confirmada hoje" de "escrita uma vez há quarenta dias", que é
|
|
291
|
+
* exatamente o problema que o frescor foi criado para resolver. A mesma classe do `work`, e eu a
|
|
292
|
+
* reintroduzi ao consertar a primeira.
|
|
293
|
+
*
|
|
294
|
+
* O degrau responde quanto acreditamos; o frescor responde se ainda é verdade. As duas coisas vão
|
|
295
|
+
* juntas ou nenhuma das duas significa algo.
|
|
296
|
+
*/
|
|
297
|
+
const fresh = (m) => {
|
|
298
|
+
if (m.type === "rationale")
|
|
299
|
+
return "stated by a person, never measured";
|
|
300
|
+
if (m.evidence === "measured" && m.confirmedAt)
|
|
301
|
+
return `your code backed this on ${m.confirmedAt.slice(0, 10)}`;
|
|
302
|
+
if (m.evidence === "declared")
|
|
303
|
+
return "declared, not measured yet";
|
|
304
|
+
if (m.evidence === "imported")
|
|
305
|
+
return "read from your code at import";
|
|
306
|
+
return "no measurement on record";
|
|
307
|
+
};
|
|
308
|
+
const lines = out.carried.map((m) => `- ${m.text}${m.applies.length > 0 ? ` [${m.applies.join(", ")}]` : ""} (${m.rung} · ${fresh(m)})`);
|
|
263
309
|
const grouped = out.groups.length > 0
|
|
264
310
|
? `\n\nSame subject, so they travel together: ${out.groups
|
|
265
311
|
.map((g) => g.members.join(" + "))
|
|
266
312
|
.join(" · ")}`
|
|
267
313
|
: "";
|
|
268
|
-
return answer(`${lines.join("\n")}${grouped}\n\n${out.because}`, "memory:recall");
|
|
314
|
+
return answer(`${work}${lines.join("\n")}${grouped}\n\n${out.because}`, "memory:recall");
|
|
269
315
|
}
|
package/package.json
CHANGED