synthesisui 0.16.28 → 0.16.30
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/doctor.js +17 -2
- package/dist/commands/mcp.js +37 -2
- package/dist/commands/sync.js +45 -1
- package/dist/doctor/requests.js +87 -1
- package/package.json +1 -1
package/dist/commands/doctor.js
CHANGED
|
@@ -4,7 +4,7 @@ import { findDivergences } from "../doctor/coherence.js";
|
|
|
4
4
|
import { findFrozenBindings } from "../doctor/frozen.js";
|
|
5
5
|
import { appendEvent, readEvents, summarize } from "../doctor/ledger.js";
|
|
6
6
|
import { bindingsFromDocument, countComponents, findOverrides, } from "../doctor/overrides.js";
|
|
7
|
-
import { readRequests } from "../doctor/requests.js";
|
|
7
|
+
import { checkableName, readRequests, verifyAndCloseRequests, } from "../doctor/requests.js";
|
|
8
8
|
import { diagnose, scanSource, siblingTokens, } from "../doctor/scan.js";
|
|
9
9
|
import { findSelfConflicts, forbiddenProps, isReset, propMatchesLabel, } from "../doctor/self-conflict.js";
|
|
10
10
|
import { buildTable, EMPTY_TABLE, nearestToken, } from "../doctor/tokens.js";
|
|
@@ -575,12 +575,24 @@ export async function doctor(opts) {
|
|
|
575
575
|
* that actually hit the gap.
|
|
576
576
|
*/
|
|
577
577
|
if (hasSystem && scopes.length === 0) {
|
|
578
|
+
// Verification first: whether an ask is still missing is a FACT about
|
|
579
|
+
// the installed css, so the queue re-checks before it reports. A request
|
|
580
|
+
// the build now delivers closes itself and says so - nobody marks done
|
|
581
|
+
// what a machine can see is done (dono, 29/07).
|
|
582
|
+
const verified = await verifyAndCloseRequests(root);
|
|
583
|
+
if (verified.length > 0) {
|
|
584
|
+
console.log("");
|
|
585
|
+
console.log(section("Requests verified"));
|
|
586
|
+
for (const r of verified) {
|
|
587
|
+
console.log(body(` ✓ ${r.id} ${checkableName(r) ?? r.name} - the installed system covers it now. Closed.`));
|
|
588
|
+
}
|
|
589
|
+
}
|
|
578
590
|
const requests = await readRequests(root);
|
|
579
591
|
if (requests.length > 0) {
|
|
580
592
|
console.log("");
|
|
581
593
|
console.log(section("What your agent asked for"));
|
|
582
594
|
for (const r of requests.slice(0, 8)) {
|
|
583
|
-
console.log(body(` ${r.id} ${r.kind === "token" ? `token ${r.value} as ${r.name}` : `component "${r.name}"`}`));
|
|
595
|
+
console.log(body(` ${r.id} ${r.kind === "token" ? `token ${r.value} as ${r.name}` : `component "${r.name}"`}${r.area === "platform" ? " [platform]" : ""}`));
|
|
584
596
|
console.log(body(` for: ${r.purpose}`));
|
|
585
597
|
if (r.considered)
|
|
586
598
|
console.log(body(` considered: ${r.considered}`));
|
|
@@ -590,6 +602,9 @@ export async function doctor(opts) {
|
|
|
590
602
|
}
|
|
591
603
|
console.log("");
|
|
592
604
|
console.log(body("Author it in the studio, or close it: synthesisui request --done <id>"));
|
|
605
|
+
if (requests.some((r) => r.area === "platform")) {
|
|
606
|
+
console.log(body("[platform] items are the synthesisui team's - no action needed; they close themselves when an update delivers."));
|
|
607
|
+
}
|
|
593
608
|
}
|
|
594
609
|
}
|
|
595
610
|
/**
|
package/dist/commands/mcp.js
CHANGED
|
@@ -220,19 +220,54 @@ async function callTool(root, name, args) {
|
|
|
220
220
|
return text(`Filed as ${r.id}. It shows up in \`synthesisui doctor\` until a person authors it or closes it - keep building with your workaround, and say in your summary that the request exists.`);
|
|
221
221
|
}
|
|
222
222
|
case "request_token": {
|
|
223
|
+
const name = String(args.name ?? "");
|
|
224
|
+
// TRIAGE AT FILING: if the system's own contract already promises this
|
|
225
|
+
// name (a keyframe exists, so `animate-<it>` should too), the gap is
|
|
226
|
+
// DELIVERY - ours, not the author's. It files routed to the platform,
|
|
227
|
+
// the owner is never asked to act, and it closes itself the moment a
|
|
228
|
+
// doctor/sync finds the promise kept in the installed css.
|
|
229
|
+
const area = (await isPromisedByContract(root, name))
|
|
230
|
+
? "platform"
|
|
231
|
+
: undefined;
|
|
223
232
|
const r = await fileRequest(root, {
|
|
224
233
|
kind: "token",
|
|
225
|
-
name
|
|
234
|
+
name,
|
|
226
235
|
purpose: String(args.purpose ?? ""),
|
|
227
236
|
value: String(args.value ?? ""),
|
|
228
237
|
file: args.file ? String(args.file) : undefined,
|
|
238
|
+
...(area ? { area } : null),
|
|
229
239
|
});
|
|
230
|
-
return text(
|
|
240
|
+
return text(area
|
|
241
|
+
? `Filed as ${r.id} and routed to the synthesisui platform team - the system's contract already promises this and the shipped css does not deliver it. No one needs to act: it closes itself when an update lands. Keep the quiet base meanwhile.`
|
|
242
|
+
: `Filed as ${r.id}. Do not add the token yourself - the request shows up in \`synthesisui doctor\` for a person to decide.`);
|
|
231
243
|
}
|
|
232
244
|
default:
|
|
233
245
|
return text(`No tool named ${name}.`, true);
|
|
234
246
|
}
|
|
235
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Does the installed system's own contract promise this token name? The one
|
|
250
|
+
* case a machine can be sure of today: `animate-<x>` where `<x>` is a
|
|
251
|
+
* keyframe the document declares - the vocabulary section is generated FROM
|
|
252
|
+
* those keyframes, so asking for one means the promise exists and the
|
|
253
|
+
* artifact failed it (the exact shape of the first field-filed request,
|
|
254
|
+
* hvdoyc, 29/07).
|
|
255
|
+
*/
|
|
256
|
+
async function isPromisedByContract(root, name) {
|
|
257
|
+
const m = /animate-([a-z0-9-]+)/.exec(name);
|
|
258
|
+
if (!m)
|
|
259
|
+
return false;
|
|
260
|
+
const kebab = (v) => v.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
261
|
+
const { documents } = await loadSystem(root);
|
|
262
|
+
for (const raw of documents) {
|
|
263
|
+
const doc = raw;
|
|
264
|
+
for (const k of Object.keys(doc.motion?.keyframes ?? {})) {
|
|
265
|
+
if (kebab(k) === m[1])
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
236
271
|
/**
|
|
237
272
|
* One request in, one response out - or null for a notification, which MUST
|
|
238
273
|
* NOT be answered. Replying to `notifications/initialized` is the classic way
|
package/dist/commands/sync.js
CHANGED
|
@@ -2,8 +2,27 @@ import { readdir, readFile } from "node:fs/promises";
|
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { readToken, resolveRegistry } from "../config.js";
|
|
4
4
|
import { readEvents } from "../doctor/ledger.js";
|
|
5
|
-
import { readRequests } from "../doctor/requests.js";
|
|
5
|
+
import { checkableName, closeRequest, readRequests, verifyAndCloseRequests, } from "../doctor/requests.js";
|
|
6
6
|
import { body, section } from "../output.js";
|
|
7
|
+
/**
|
|
8
|
+
* What each decision means ON THIS MACHINE - the card decides, the sync
|
|
9
|
+
* enacts. The web never touches the local file; this line is printed right
|
|
10
|
+
* after `closeRequest` runs here, so the person knows what happened and what
|
|
11
|
+
* (if anything) is theirs to do next.
|
|
12
|
+
*/
|
|
13
|
+
export function decisionLine(d, slug) {
|
|
14
|
+
const note = d.note ? ` - "${d.note}"` : "";
|
|
15
|
+
switch (d.status) {
|
|
16
|
+
case "authored":
|
|
17
|
+
return ` ✓ ${d.id} authored on the platform${note}. Get it: npx synthesisui@latest upgrade ${slug}`;
|
|
18
|
+
case "declined":
|
|
19
|
+
return ` ✕ ${d.id} declined${note}. The boundary stands - the system stays without it.`;
|
|
20
|
+
case "platform":
|
|
21
|
+
return ` → ${d.id} routed to the synthesisui team${note}. It is their queue now.`;
|
|
22
|
+
default:
|
|
23
|
+
return ` · ${d.id} answered${note}.`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
7
26
|
/**
|
|
8
27
|
* `synthesisui sync` - the ledger travels, because a person said so.
|
|
9
28
|
*
|
|
@@ -63,6 +82,10 @@ export async function sync(opts) {
|
|
|
63
82
|
console.log(body("No installed system here, so there is nowhere to sync to."));
|
|
64
83
|
return;
|
|
65
84
|
}
|
|
85
|
+
// Verification first, so the snapshot never reports "open" about something
|
|
86
|
+
// the installed css already delivers - a request closes because a machine
|
|
87
|
+
// CHECKED, not because someone remembered to say so (dono, 29/07).
|
|
88
|
+
const verified = await verifyAndCloseRequests(root);
|
|
66
89
|
const events = await readEvents(root);
|
|
67
90
|
const requests = await readRequests(root);
|
|
68
91
|
if (events.length === 0 && requests.length === 0) {
|
|
@@ -92,6 +115,27 @@ export async function sync(opts) {
|
|
|
92
115
|
const out = (await res.json());
|
|
93
116
|
console.log(section("Sync"));
|
|
94
117
|
console.log(body(`${out.eventsReceived} checks sent, ${out.eventsNew} new. ${out.requestsNow} open request${out.requestsNow === 1 ? "" : "s"}.`));
|
|
118
|
+
// Closed by CHECKING, not by word: the installed css now delivers these.
|
|
119
|
+
if (verified.length > 0) {
|
|
120
|
+
console.log("");
|
|
121
|
+
console.log(body("Verified against the installed css, closed here:"));
|
|
122
|
+
for (const r of verified) {
|
|
123
|
+
console.log(body(` ✓ ${r.id} ${checkableName(r) ?? r.name} - delivered. Nothing for anyone to do.`));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Decisions travel BACK: the owner answered on the card, and this - the
|
|
127
|
+
// person's own sync, on their machine - is what closes the local file.
|
|
128
|
+
// Nobody hunts an id: deciding on the card and running sync is the whole
|
|
129
|
+
// gesture.
|
|
130
|
+
const decisions = out.decisions ?? [];
|
|
131
|
+
if (decisions.length > 0) {
|
|
132
|
+
console.log("");
|
|
133
|
+
console.log(body("Answered on the platform, closed here:"));
|
|
134
|
+
for (const d of decisions) {
|
|
135
|
+
await closeRequest(root, d.id);
|
|
136
|
+
console.log(body(decisionLine(d, slug)));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
95
139
|
console.log("");
|
|
96
140
|
console.log(body(`The record lives on your system's overview:`));
|
|
97
141
|
console.log(body(` ${base}/dashboard/mine/${slug}`));
|
package/dist/doctor/requests.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appendFile, readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
import { appendFile, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
/**
|
|
4
4
|
* RECUSA → PEDIDO: the refusal becomes the request queue.
|
|
@@ -72,3 +72,89 @@ export async function closeRequest(root, id) {
|
|
|
72
72
|
await writeFile(path(root), keep.length ? `${keep.map((r) => JSON.stringify(r)).join("\n")}\n` : "", "utf8");
|
|
73
73
|
return true;
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* REQUESTS CLOSE BY VERIFICATION, NOT BY WORD (dono, 29/07: "não deveria ser
|
|
77
|
+
* automático e verificado no deploy?").
|
|
78
|
+
*
|
|
79
|
+
* A token request names what was missing. Whether it is still missing is a
|
|
80
|
+
* FACT about the installed css, so the queue re-checks it on every doctor and
|
|
81
|
+
* sync: once `--animate-rise` (or `--ds-<name>`) exists in what is actually
|
|
82
|
+
* installed, the request closes itself and says so. Trust-based closing
|
|
83
|
+
* ("Mark authored") remains only for what a machine cannot check.
|
|
84
|
+
*
|
|
85
|
+
* Only the conservative direction is automated: a false "still open" costs a
|
|
86
|
+
* glance; a false "satisfied" silently buries a real gap. Component requests
|
|
87
|
+
* and prose-named tokens stay manual.
|
|
88
|
+
*/
|
|
89
|
+
/** The name a token request is checkable by: its first kebab-case run.
|
|
90
|
+
* Agents write prose around it ("animate-rise (and animate-fade / ...)"). */
|
|
91
|
+
export function checkableName(req) {
|
|
92
|
+
if (req.kind !== "token")
|
|
93
|
+
return null;
|
|
94
|
+
const m = /[a-z][a-z0-9]*(?:-[a-z0-9]+)+/.exec(req.name.trim());
|
|
95
|
+
return m ? m[0] : null;
|
|
96
|
+
}
|
|
97
|
+
/** Requests whose ask the installed css now demonstrably delivers. */
|
|
98
|
+
export function satisfiedRequests(requests, installedCss) {
|
|
99
|
+
return requests.filter((r) => {
|
|
100
|
+
const name = checkableName(r);
|
|
101
|
+
if (!name)
|
|
102
|
+
return false;
|
|
103
|
+
const candidates = name.startsWith("animate-")
|
|
104
|
+
? [`--${name}`]
|
|
105
|
+
: [`--ds-${name}`, `--${name}`];
|
|
106
|
+
return candidates.some((c) => installedCss.includes(`${c}:`));
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Everything the pinned install actually ships, as one string - tokens.css
|
|
111
|
+
* AND theme.css of the locked version, for every installed system. This is
|
|
112
|
+
* the ground verification stands on: not the doc, not the GUIDE's promises,
|
|
113
|
+
* the css a build would really read.
|
|
114
|
+
*/
|
|
115
|
+
export async function readInstalledCss(root) {
|
|
116
|
+
const dsDir = join(root, "_synthesisui", "ds");
|
|
117
|
+
let css = "";
|
|
118
|
+
let slugs = [];
|
|
119
|
+
try {
|
|
120
|
+
slugs = (await readdir(dsDir, { withFileTypes: true }))
|
|
121
|
+
.filter((e) => e.isDirectory())
|
|
122
|
+
.map((e) => e.name);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return "";
|
|
126
|
+
}
|
|
127
|
+
for (const s of slugs) {
|
|
128
|
+
const raw = await readFile(join(dsDir, s, ".lock"), "utf8").catch(() => "");
|
|
129
|
+
let version;
|
|
130
|
+
try {
|
|
131
|
+
version = JSON.parse(raw).version;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// no lock, no pinned folder to read
|
|
135
|
+
}
|
|
136
|
+
if (!version)
|
|
137
|
+
continue;
|
|
138
|
+
for (const f of ["tokens.css", "theme.css"]) {
|
|
139
|
+
css += `\n${await readFile(join(dsDir, s, `v${version}`, f), "utf8").catch(() => "")}`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return css;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The whole gesture: read, verify against the installed css, close what is
|
|
146
|
+
* now delivered, and report each closure. Shared by doctor and sync so the
|
|
147
|
+
* queue never says "open" about something the build already ships.
|
|
148
|
+
*/
|
|
149
|
+
export async function verifyAndCloseRequests(root) {
|
|
150
|
+
const requests = await readRequests(root);
|
|
151
|
+
if (requests.length === 0)
|
|
152
|
+
return [];
|
|
153
|
+
const css = await readInstalledCss(root);
|
|
154
|
+
if (!css)
|
|
155
|
+
return [];
|
|
156
|
+
const satisfied = satisfiedRequests(requests, css);
|
|
157
|
+
for (const r of satisfied)
|
|
158
|
+
await closeRequest(root, r.id);
|
|
159
|
+
return satisfied;
|
|
160
|
+
}
|
package/package.json
CHANGED