synthesisui 0.16.137 → 0.16.139
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/commands/mcp.js +36 -8
- package/dist/commands/sync.js +13 -1
- package/dist/index.js +4 -2
- package/dist/repo-state.js +64 -0
- package/dist/skill-import.js +51 -1
- package/package.json +1 -1
package/dist/commands/mcp.js
CHANGED
|
@@ -4,6 +4,7 @@ import { readToken, resolveRegistry } from "../config.js";
|
|
|
4
4
|
import { fileRequest } from "../doctor/requests.js";
|
|
5
5
|
import { diagnose, scanSource } from "../doctor/scan.js";
|
|
6
6
|
import { nearestToken, tokenFor } from "../doctor/tokens.js";
|
|
7
|
+
import { repoStateOf } from "../repo-state.js";
|
|
7
8
|
import { component } from "./component.js";
|
|
8
9
|
import { loadSystem, walkAll } from "./doctor.js";
|
|
9
10
|
const send = (msg) => {
|
|
@@ -577,7 +578,9 @@ async function validateRecipes(root, entries) {
|
|
|
577
578
|
* verdade. Nunca bloqueia a resposta: sem sessão ou sem rede, não conta e não quebra -
|
|
578
579
|
* telemetria que atrasa a ferramenta é telemetria que alguém desliga.
|
|
579
580
|
*/
|
|
580
|
-
function countAgentRead(root, component, action
|
|
581
|
+
function countAgentRead(root, component, action,
|
|
582
|
+
/** A versão que está reportando - ver `repoStateOf`. Ausente = não reporta estado. */
|
|
583
|
+
cli) {
|
|
581
584
|
void (async () => {
|
|
582
585
|
try {
|
|
583
586
|
const token = await readToken();
|
|
@@ -604,6 +607,7 @@ function countAgentRead(root, component, action) {
|
|
|
604
607
|
}
|
|
605
608
|
if (!slug)
|
|
606
609
|
return;
|
|
610
|
+
const repo = cli ? await repoStateOf(root, slug, cli) : null;
|
|
607
611
|
const base = resolveRegistry();
|
|
608
612
|
await fetch(`${base}/api/telemetry/agent-read`, {
|
|
609
613
|
method: "POST",
|
|
@@ -611,7 +615,20 @@ function countAgentRead(root, component, action) {
|
|
|
611
615
|
Authorization: `Bearer ${token}`,
|
|
612
616
|
"content-type": "application/json",
|
|
613
617
|
},
|
|
614
|
-
|
|
618
|
+
/**
|
|
619
|
+
* O ESTADO DO REPO PEGA CARONA AQUI - ver `repoStateOf`.
|
|
620
|
+
*
|
|
621
|
+
* Este ping já é automático e já manda slug/componente/ação em toda leitura de receita
|
|
622
|
+
* pelo agente. "Em que versão eu estou" é o mesmo tipo de sinal, no mesmo momento, e
|
|
623
|
+
* assim a tela tem o número sem depender de o cliente lembrar de rodar `sync` - que é
|
|
624
|
+
* manual de propósito e vai continuar sendo.
|
|
625
|
+
*/
|
|
626
|
+
body: JSON.stringify({
|
|
627
|
+
slug,
|
|
628
|
+
component,
|
|
629
|
+
action,
|
|
630
|
+
...(repo ? { repo } : {}),
|
|
631
|
+
}),
|
|
615
632
|
});
|
|
616
633
|
}
|
|
617
634
|
catch {
|
|
@@ -798,7 +815,9 @@ async function addComponent(root, name) {
|
|
|
798
815
|
return `${out}\n\nIt is real code in this project now - import it and extend it rather than writing your own.`;
|
|
799
816
|
}
|
|
800
817
|
// ── The protocol ─────────────────────────────────────────────────────────────
|
|
801
|
-
async function callTool(root, name, args
|
|
818
|
+
async function callTool(root, name, args,
|
|
819
|
+
/** A versão que reporta o estado do repo no ping - ver `countAgentRead`. */
|
|
820
|
+
cli) {
|
|
802
821
|
switch (name) {
|
|
803
822
|
case "check_file":
|
|
804
823
|
return text(await checkFile(root, String(args.path ?? ".")));
|
|
@@ -807,10 +826,10 @@ async function callTool(root, name, args) {
|
|
|
807
826
|
case "list_components":
|
|
808
827
|
return text(await listComponents(root));
|
|
809
828
|
case "describe_component":
|
|
810
|
-
countAgentRead(root, String(args.name ?? ""), "describe");
|
|
829
|
+
countAgentRead(root, String(args.name ?? ""), "describe", cli);
|
|
811
830
|
return text(await describeComponent(root, String(args.name ?? "")));
|
|
812
831
|
case "add_component":
|
|
813
|
-
countAgentRead(root, String(args.name ?? ""), "add");
|
|
832
|
+
countAgentRead(root, String(args.name ?? ""), "add", cli);
|
|
814
833
|
return text(await addComponent(root, String(args.name ?? "")));
|
|
815
834
|
case "recipe_vocabulary":
|
|
816
835
|
return text(await recipeVocabulary());
|
|
@@ -893,7 +912,12 @@ async function isPromisedByContract(root, name) {
|
|
|
893
912
|
* Split out from the stdin loop so the protocol can be exercised without a
|
|
894
913
|
* pipe: the loop below is framing, this is the protocol.
|
|
895
914
|
*/
|
|
896
|
-
export async function handleRequest(root, req
|
|
915
|
+
export async function handleRequest(root, req,
|
|
916
|
+
/**
|
|
917
|
+
* A versão que reporta o estado do repo no ping do agente - ver `repoStateOf`. Opcional porque os
|
|
918
|
+
* specs chamam esta função direto, e ali a versão não é o que está sob teste.
|
|
919
|
+
*/
|
|
920
|
+
cli) {
|
|
897
921
|
const id = req.id;
|
|
898
922
|
if (id === undefined)
|
|
899
923
|
return null;
|
|
@@ -919,7 +943,11 @@ export async function handleRequest(root, req) {
|
|
|
919
943
|
case "tools/call": {
|
|
920
944
|
const name = String(req.params?.name ?? "");
|
|
921
945
|
const args = (req.params?.arguments ?? {});
|
|
922
|
-
return {
|
|
946
|
+
return {
|
|
947
|
+
jsonrpc: "2.0",
|
|
948
|
+
id,
|
|
949
|
+
result: await callTool(root, name, args, cli),
|
|
950
|
+
};
|
|
923
951
|
}
|
|
924
952
|
default:
|
|
925
953
|
return {
|
|
@@ -960,7 +988,7 @@ export async function mcp(opts) {
|
|
|
960
988
|
catch {
|
|
961
989
|
continue; // Unparseable frame with no id: nobody to answer.
|
|
962
990
|
}
|
|
963
|
-
const res = await handleRequest(root, req);
|
|
991
|
+
const res = await handleRequest(root, req, opts.cli);
|
|
964
992
|
if (res)
|
|
965
993
|
send(res);
|
|
966
994
|
}
|
package/dist/commands/sync.js
CHANGED
|
@@ -4,6 +4,7 @@ import { readToken, resolveRegistry } from "../config.js";
|
|
|
4
4
|
import { readEvents } from "../doctor/ledger.js";
|
|
5
5
|
import { checkableName, closeRequest, readRequests, verifyAndCloseRequests, } from "../doctor/requests.js";
|
|
6
6
|
import { body, section } from "../output.js";
|
|
7
|
+
import { repoStateOf } from "../repo-state.js";
|
|
7
8
|
/**
|
|
8
9
|
* What each decision means ON THIS MACHINE - the card decides, the sync
|
|
9
10
|
* enacts. The web never touches the local file; this line is printed right
|
|
@@ -87,6 +88,7 @@ export async function sync(opts) {
|
|
|
87
88
|
// CHECKED, not because someone remembered to say so (dono, 29/07).
|
|
88
89
|
const verified = await verifyAndCloseRequests(root);
|
|
89
90
|
const events = await readEvents(root);
|
|
91
|
+
const repo = opts.cli ? await repoStateOf(root, slug, opts.cli) : null;
|
|
90
92
|
const requests = await readRequests(root);
|
|
91
93
|
if (events.length === 0 && requests.length === 0) {
|
|
92
94
|
console.log(section("Sync"));
|
|
@@ -99,7 +101,17 @@ export async function sync(opts) {
|
|
|
99
101
|
"content-type": "application/json",
|
|
100
102
|
Authorization: `Bearer ${token}`,
|
|
101
103
|
},
|
|
102
|
-
|
|
104
|
+
/**
|
|
105
|
+
* O ESTADO DO REPO VAI JUNTO - ver `repoStateOf`. É o `sync` que ELE dispara, então nada
|
|
106
|
+
* passou a ser enviado sem que já fosse: a versão instalada viaja no mesmo pacote que o
|
|
107
|
+
* registro de checagens, e é o que permite a tela dizer "você está em v2 e existe v4".
|
|
108
|
+
*/
|
|
109
|
+
body: JSON.stringify({
|
|
110
|
+
slug,
|
|
111
|
+
events,
|
|
112
|
+
requests,
|
|
113
|
+
...(repo ? { repo } : {}),
|
|
114
|
+
}),
|
|
103
115
|
}).catch(() => null);
|
|
104
116
|
if (!res) {
|
|
105
117
|
console.log(section("Sync"));
|
package/dist/index.js
CHANGED
|
@@ -235,7 +235,8 @@ async function main() {
|
|
|
235
235
|
// Long-lived: it owns stdin/stdout until the client closes the pipe, so
|
|
236
236
|
// it must not be reached by anything that prints.
|
|
237
237
|
case "mcp":
|
|
238
|
-
|
|
238
|
+
// A versão que reporta o estado do repo no ping do agente - ver `repoStateOf`.
|
|
239
|
+
await mcp({ dir, cli: CLI_VERSION });
|
|
239
240
|
return;
|
|
240
241
|
case "doctor":
|
|
241
242
|
// positional paths scope the READING (the system is still found from the
|
|
@@ -421,7 +422,8 @@ async function main() {
|
|
|
421
422
|
await clean({ dir, force: flags.force === true });
|
|
422
423
|
break;
|
|
423
424
|
case "sync":
|
|
424
|
-
|
|
425
|
+
// A versão que reporta o estado do repo - ver `repoStateOf`.
|
|
426
|
+
await sync({ dir, registry, cli: CLI_VERSION });
|
|
425
427
|
break;
|
|
426
428
|
case "request": {
|
|
427
429
|
// `synthesisui request` lists; `request component --name x --for "..."`
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O ESTADO DO REPO DELE, medido no repo dele.
|
|
3
|
+
*
|
|
4
|
+
* A plataforma sabe o que publicou e não sabia em que versão o repo do cliente está - então a tela
|
|
5
|
+
* não podia escrever "você está em v2 e existe v4", porque ela não tinha o primeiro número (dono,
|
|
6
|
+
* 05/08).
|
|
7
|
+
*
|
|
8
|
+
* O `.lock` de cada DS instalado (`_synthesisui/ds/<slug>/.lock`) tem esse número desde sempre. O
|
|
9
|
+
* que faltava era ele viajar.
|
|
10
|
+
*
|
|
11
|
+
* E ELE VIAJA NOS CANAIS QUE JÁ FALAM, nunca num novo:
|
|
12
|
+
*
|
|
13
|
+
* `sync` manual, e é assim que a landing promete. Leva o registro local
|
|
14
|
+
* inteiro quando ELE decide.
|
|
15
|
+
* ping do MCP automático, e já existe: toda leitura de receita pelo agente já
|
|
16
|
+
* manda slug/componente/ação. "Em que versão eu estou" é o mesmo
|
|
17
|
+
* tipo de sinal, no mesmo momento.
|
|
18
|
+
*
|
|
19
|
+
* Nada passou a ser enviado que já não fosse, e nenhum canal novo foi aberto. Transformar o `sync`
|
|
20
|
+
* em telemetria silenciosa mudaria o contrato de privacidade da landing, e isso não se faz de lado.
|
|
21
|
+
*
|
|
22
|
+
* NADA POR PESSOA. Nem nome de branch, nem autor, nem caminho de arquivo: só a versão instalada, a
|
|
23
|
+
* versão do CLI, e quando o doctor rodou. Um painel que ranqueia desenvolvedor transforma a
|
|
24
|
+
* checagem em adversário, e adversário é desinstalado.
|
|
25
|
+
*/
|
|
26
|
+
import { readFile } from "node:fs/promises";
|
|
27
|
+
import { join } from "node:path";
|
|
28
|
+
import { readEvents } from "./doctor/ledger.js";
|
|
29
|
+
/**
|
|
30
|
+
* O estado de um slug neste projeto, ou nada quando não há `.lock`.
|
|
31
|
+
*
|
|
32
|
+
* Silencioso de propósito: um projeto sem o DS instalado não é um erro a reportar, e um `.lock`
|
|
33
|
+
* ilegível não vale derrubar um `sync` que existe para levar o registro de checagens.
|
|
34
|
+
*/
|
|
35
|
+
export async function repoStateOf(projectRoot, slug, cli) {
|
|
36
|
+
const lock = await readJson(join(projectRoot, "_synthesisui", "ds", slug, ".lock"));
|
|
37
|
+
/**
|
|
38
|
+
* O ÚLTIMO DOCTOR, PELO MESMO LEITOR QUE O `sync` USA (`readEvents`). Abrir o arquivo por conta
|
|
39
|
+
* própria aqui seria uma segunda implementação do formato do registro - e a primeira coisa que
|
|
40
|
+
* ela faria é discordar da outra sobre o que é uma linha válida.
|
|
41
|
+
*/
|
|
42
|
+
const doctor = (await readEvents(projectRoot))
|
|
43
|
+
.filter((e) => e.kind === "doctor" && e.at)
|
|
44
|
+
.sort((a, b) => String(b.at).localeCompare(String(a.at)))[0];
|
|
45
|
+
const payload = {
|
|
46
|
+
...(typeof lock?.version === "number" ? { installed: lock.version } : {}),
|
|
47
|
+
...(lock?.adopted === true ? { adopted: true } : {}),
|
|
48
|
+
cli,
|
|
49
|
+
...(doctor?.at ? { doctorAt: doctor.at } : {}),
|
|
50
|
+
...(typeof doctor?.phantoms === "number"
|
|
51
|
+
? { drifts: doctor.phantoms }
|
|
52
|
+
: {}),
|
|
53
|
+
};
|
|
54
|
+
/** Só o `cli` não é estado: sem nada do repo, não há o que reportar. */
|
|
55
|
+
return Object.keys(payload).length > 1 ? payload : null;
|
|
56
|
+
}
|
|
57
|
+
async function readJson(path) {
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
package/dist/skill-import.js
CHANGED
|
@@ -74,6 +74,31 @@ and narrating it - *"registry is production, so no --registry flag needed"* - sp
|
|
|
74
74
|
person's attention on a decision that was never theirs (dono, 01/08). Say something only when
|
|
75
75
|
there is nothing to say to: no token, or the wrong host.
|
|
76
76
|
|
|
77
|
+
### 0b. ASK THE REGISTRY WHAT EXISTS - never your notes
|
|
78
|
+
|
|
79
|
+
If your memory says this repo already produced a system, **verify it before you say it out loud**,
|
|
80
|
+
and verify it per SLUG:
|
|
81
|
+
|
|
82
|
+
\`\`\`
|
|
83
|
+
curl -s -o /dev/null -w "%{http_code}" <registry>/api/registry/ds/<slug>
|
|
84
|
+
\`\`\`
|
|
85
|
+
|
|
86
|
+
\`404\` means it does not exist. \`200\` means it does. That is the whole check, and it costs one call.
|
|
87
|
+
|
|
88
|
+
**\`synthesisui list\` does NOT answer this.** It returns the public gallery - 13 systems, none of
|
|
89
|
+
them private - so a private system of theirs is invisible to it and a \`list\` that does not mention
|
|
90
|
+
\`their-slug\` proves nothing.
|
|
91
|
+
|
|
92
|
+
This exists because of a real run (dono, 05/08). The owner had deleted every system on his account,
|
|
93
|
+
and the skill opened with *"my notes from the previous twelve runs say this repo has already
|
|
94
|
+
produced two published systems"* - then offered three options built on that: upgrade a slug the
|
|
95
|
+
registry answers \`404\` for, avoid a name collision that no longer existed, and warn that a \`--dry\`
|
|
96
|
+
would wipe files it does not touch. One \`curl\` would have replaced all of it.
|
|
97
|
+
|
|
98
|
+
**A memory may report the past; it may never assert the present.** *"The last run produced
|
|
99
|
+
\`signalui\`"* is a fine sentence. *"This repo has two published systems"* is a claim about the server,
|
|
100
|
+
and the server is the only thing that can make it.
|
|
101
|
+
|
|
77
102
|
## How to narrate
|
|
78
103
|
|
|
79
104
|
The person cares about **what you found in their code** and **what you are about to create**.
|
|
@@ -880,6 +905,13 @@ Copying \`dark\` in at the top level of a recipe costs you that block, silently,
|
|
|
880
905
|
component that has one - and \`import\` already converts it for the recipes IT writes, so the
|
|
881
906
|
only place this can be lost is a recipe you hand-built (not-expressed.md, dono, 03/08).
|
|
882
907
|
|
|
908
|
+
**Which means: do NOT pre-convert the dark half inside \`looks[X]\`.** The conversion already
|
|
909
|
+
happens on the way in, so adding a \`scheme: "dark"\` layer to the census's own look while leaving
|
|
910
|
+
the \`dark\` key in place produces the SAME layer twice - measured at 28 components in one real
|
|
911
|
+
import, with the properties in a different key order so it did not even look like a duplicate
|
|
912
|
+
(dono, 05/08). \`validate_recipe\` telling you a top-level \`dark\` would be dropped is true about a
|
|
913
|
+
RECIPE and says nothing about the reading: you send a reading, and the reading's \`dark\` is read.
|
|
914
|
+
|
|
883
915
|
**In ONE call.** Build every recipe in memory, then send the whole reading to
|
|
884
916
|
\`validate_recipes\` - one round trip for 35 components instead of 35. \`validate_recipe\`
|
|
885
917
|
(singular) is for the one recipe you rewrote after the batch named it; reaching for it in a
|
|
@@ -1162,10 +1194,20 @@ and pass on the ones that matter.
|
|
|
1162
1194
|
The system is only worth something once it governs the code it came from:
|
|
1163
1195
|
|
|
1164
1196
|
\`\`\`
|
|
1165
|
-
npx synthesisui
|
|
1197
|
+
npx synthesisui add <slug> [--registry …] # INSTALLS it: writes _synthesisui/ds/<slug>/
|
|
1166
1198
|
npx synthesisui doctor [paths…] # the contract starts being enforced
|
|
1199
|
+
npx synthesisui upgrade <slug> [--registry …] # LATER, when a new version is published
|
|
1167
1200
|
\`\`\`
|
|
1168
1201
|
|
|
1202
|
+
**The order is not a preference: \`upgrade\` reads the \`.lock\` of an installed DS and refuses
|
|
1203
|
+
without one** - \`"<slug>" is not installed here - run \\\`synthesisui add <slug>\\\` first.\` So
|
|
1204
|
+
offering \`upgrade\` to somebody who has never installed hands them a command that fails on contact
|
|
1205
|
+
(dono, 05/08). Check for \`_synthesisui/ds/<slug>/.lock\` before you recommend either one.
|
|
1206
|
+
|
|
1207
|
+
And say what each writes, because one of them rewrites code: \`add\` and \`upgrade\` write inside
|
|
1208
|
+
\`_synthesisui/\`, and \`upgrade\` ALSO regenerates components - but only files carrying the
|
|
1209
|
+
\`Generated by SynthesisUI\` header, so their own components are never touched.
|
|
1210
|
+
|
|
1169
1211
|
\`doctor\` is where the promise pays off: every design value written by hand, the token their
|
|
1170
1212
|
own system already has for it, and the components used outside the axes their contract
|
|
1171
1213
|
declares. In a monorepo, scope it per app - one system, N consumers.
|
|
@@ -1186,8 +1228,16 @@ The user should end up with a system that reads like theirs and not like ours:
|
|
|
1186
1228
|
|
|
1187
1229
|
## What to resist
|
|
1188
1230
|
|
|
1231
|
+
- **Do not let memory speak for the server.** Notes report the past; a claim about what exists
|
|
1232
|
+
now needs the one \`curl\` in step 0b. Four statements in one real run were wrong this way, and
|
|
1233
|
+
every option offered on top of them was wrong too (dono, 05/08).
|
|
1189
1234
|
- **Do not run \`import\` without \`--dry\` to explore.** It creates a real system on their
|
|
1190
1235
|
account. Measure with \`--dry\`, read, then send once.
|
|
1236
|
+
- **\`--dry\` does not delete anything.** It writes \`census.json\` and creates the folder if needed -
|
|
1237
|
+
there is no \`rm\` in the import path. What it DOES do is overwrite that one file, so a reading
|
|
1238
|
+
injected into it is lost; everything else under \`_synthesisui/\` (the config, \`ds/<slug>/\`, the
|
|
1239
|
+
hook's marker, \`not-expressed.md\`) survives untouched. Telling somebody a measure will wipe their
|
|
1240
|
+
install talks them out of a safe command (dono, 05/08).
|
|
1191
1241
|
- **Do not redo the arithmetic.** If you find yourself counting colours, you are in the wrong
|
|
1192
1242
|
half of the pipeline.
|
|
1193
1243
|
- **Do not fill \`roles\` with what looks nice.** It is a reading of what their code already
|
package/package.json
CHANGED