synthesisui 0.16.487 → 0.16.488
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 +16 -2
- package/dist/commands/add.js +14 -1
- package/dist/commands/mcp.js +69 -2
- package/dist/commands/preferences.js +75 -0
- package/dist/first-theme.js +48 -0
- package/dist/index.js +13 -2
- package/dist/install-marks.js +6 -1
- package/dist/preferences.js +200 -0
- package/package.json +1 -1
package/dist/claude-md.js
CHANGED
|
@@ -455,6 +455,20 @@ and task, call it once - an empty result means there is nothing more to retrieve
|
|
|
455
455
|
tells you what was decided and why it matters; **the file on disk is still the authority for what is
|
|
456
456
|
written now** - read the code before you edit it, never a remembered signature.`
|
|
457
457
|
: "";
|
|
458
|
+
/**
|
|
459
|
+
* OS BLUEPRINTS DELA, LIGADOS - era "só quando ela pede" (02/10); desde 03/10 ficam ligados e se desligam por projeto.
|
|
460
|
+
* Ele: "isso tem que ficar ativado sempre e desativado se a gente precisar". No teste final a base só chegou no
|
|
461
|
+
* pedido que dizia a frase - 1 de 5. Jornada "a conta nasce com a base". O texto abaixo é o de 02/10: O
|
|
462
|
+
* blueprint de preferências mora na conta da pessoa e serve todo projeto dela; a linha diz ao agente
|
|
463
|
+
* as duas portas e a regra que as mantém fechadas no dia a dia: quem cria sem pedir continua livre.
|
|
464
|
+
* Basta o servidor registrado - a preferência não depende do sistema instalado.
|
|
465
|
+
*/
|
|
466
|
+
const preferencesLine = recall.available
|
|
467
|
+
? `
|
|
468
|
+
|
|
469
|
+
**Their page blueprints are on:** call \`preferences\` with the page's kind before building one, unless
|
|
470
|
+
they say not to (\`synthesisui preferences off\`); a correction to keep is \`remember_preference\`.`
|
|
471
|
+
: "";
|
|
458
472
|
/**
|
|
459
473
|
* O TEMA É DELE - bateria ampliada, item 22 (29/09). Em c01, c02, c05, c17 e c23 o agente mexeu no tema do projeto
|
|
460
474
|
* sem perguntar: `--text-xl` no globals.css para "casar com a escala", 17 valores no tailwind.config.js, três cores
|
|
@@ -470,7 +484,7 @@ new token invented in silence - say so instead, because a new token is a decisio
|
|
|
470
484
|
is no component index for an adopted system: the tokens ARE the contract.${theirsLine}
|
|
471
485
|
|
|
472
486
|
**What the vocabulary IS is not written here.** Fetch only the piece the task requires:
|
|
473
|
-
\`system_doctrine\` for the rules and the voice, \`find_token\` for a value you are about to write.${memoryLine}${selfCheck}`
|
|
487
|
+
\`system_doctrine\` for the rules and the voice, \`find_token\` for a value you are about to write.${memoryLine}${preferencesLine}${selfCheck}`
|
|
474
488
|
: `**These are true without asking anyone:**
|
|
475
489
|
|
|
476
490
|
- The system's vocabulary only - and it is written in the names THIS project declares. \`find_token\`
|
|
@@ -493,7 +507,7 @@ If nothing covers it, say which indexed entry you considered and why it did not
|
|
|
493
507
|
|
|
494
508
|
describe_component its parts, variants, states and the tokens it already uses
|
|
495
509
|
system_doctrine the rules and the voice of this system
|
|
496
|
-
playbook the families, and what each one requires before it can be drawn${memoryLine}${selfCheck}`
|
|
510
|
+
playbook the families, and what each one requires before it can be drawn${memoryLine}${preferencesLine}${selfCheck}`
|
|
497
511
|
: onlyAdopted
|
|
498
512
|
? `**When creating or editing components, read the system's GUIDE.md and follow it:** use the
|
|
499
513
|
project's OWN custom properties, exactly as the guide lists them. Do not write raw colours,
|
package/dist/commands/add.js
CHANGED
|
@@ -4,6 +4,7 @@ import { agentsToMaintain } from "../agents-chosen.js";
|
|
|
4
4
|
import { syncClaudeMd } from "../claude-md.js";
|
|
5
5
|
import { readProjectConfig, readToken, resolveRegistry } from "../config.js";
|
|
6
6
|
import { parseRules } from "../doctrine.js";
|
|
7
|
+
import { wireTheme } from "../first-theme.js";
|
|
7
8
|
import { customFontFamilies, googleFontsHref, nextFontSnippet, } from "../fonts.js";
|
|
8
9
|
import { detectAppDirs } from "../global-sheet.js";
|
|
9
10
|
import { lockReference } from "../group-role.js";
|
|
@@ -737,6 +738,15 @@ export async function add(slug, opts) {
|
|
|
737
738
|
: ` CLAUDE.md already current - nothing to rewrite (${claudeMd.count} system(s) indexed)`);
|
|
738
739
|
if (opts.setupHints === false)
|
|
739
740
|
return;
|
|
741
|
+
/**
|
|
742
|
+
* O TEMA LIGADO NA INSTALAÇÃO (03/10) - o `add` chamado direto liga o tema de um projeto que ainda não tem o seu, como
|
|
743
|
+
* o `init --ds` já fazia. Ver `first-theme.ts`. O `init` chama o `add` com `setupHints: false` e cuida do tema ele mesmo.
|
|
744
|
+
*/
|
|
745
|
+
const wired = await wireTheme(projectRoot, slug);
|
|
746
|
+
if (wired.written) {
|
|
747
|
+
console.log("");
|
|
748
|
+
console.log(`✓ ${wired.written}: ${wired.count} names of ${slug} declared in your own ${wired.where}, with the values written - your project had none of its own, so installing the system turns its theme on.`);
|
|
749
|
+
}
|
|
740
750
|
// ── DX: concrete paths + copy-pasteable snippets, with breathing room ──
|
|
741
751
|
// Where the app actually lives (app/ vs src/app/) still drives where `fonts.ts`
|
|
742
752
|
// lands and which layout the snippet names. The @import depth went with the
|
|
@@ -879,7 +889,10 @@ export async function add(slug, opts) {
|
|
|
879
889
|
const named = [
|
|
880
890
|
...new Set(Object.values(families)
|
|
881
891
|
.filter((f) => typeof f === "string")
|
|
882
|
-
.map((f) => f
|
|
892
|
+
.map((f) => f
|
|
893
|
+
.split(",")[0]
|
|
894
|
+
?.trim()
|
|
895
|
+
.replace(/^["']|["']$/g, "") ?? "")
|
|
883
896
|
.filter(Boolean)),
|
|
884
897
|
];
|
|
885
898
|
if (opts.fromImport) {
|
package/dist/commands/mcp.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { ourSpellingLines } from "../our-spelling.js";
|
|
3
2
|
import { readdir, readFile } from "node:fs/promises";
|
|
4
3
|
import { join, relative, resolve } from "node:path";
|
|
5
4
|
import { provenanceAction, toolSlug, withSource, } from "../agent-provenance.js";
|
|
@@ -11,11 +10,13 @@ import { declareForm } from "../doctor/declared-forms.js";
|
|
|
11
10
|
import { describeTriage, triageLedger } from "../doctor/gap-triage.js";
|
|
12
11
|
import { readEvents } from "../doctor/ledger.js";
|
|
13
12
|
import { fileRequest } from "../doctor/requests.js";
|
|
14
|
-
import { describeOffScale, diagnose, nameToWrite, scanSource } from "../doctor/scan.js";
|
|
13
|
+
import { describeOffScale, diagnose, nameToWrite, scanSource, } from "../doctor/scan.js";
|
|
15
14
|
import { nearestToken, normalizeValue, tokenFor } from "../doctor/tokens.js";
|
|
16
15
|
import { ruleLine } from "../doctrine.js";
|
|
17
16
|
import { fromCensus } from "../memory/observation.js";
|
|
18
17
|
import { handleRecall, handleRemember, MEMORY_TOOLS } from "../memory/tools.js";
|
|
18
|
+
import { ourSpellingLines } from "../our-spelling.js";
|
|
19
|
+
import { fetchPreferences, isKind, isLayer, PREFERENCE_KINDS, PREFERENCE_LAYERS, preferencesAnswer, readOff, readSelection, sendPiece, } from "../preferences.js";
|
|
19
20
|
import { readInstalledConvention } from "../project-facts.js";
|
|
20
21
|
import { repoStateOf } from "../repo-state.js";
|
|
21
22
|
import { detectStack } from "../stack.js";
|
|
@@ -361,6 +362,47 @@ const TOOLS = [
|
|
|
361
362
|
* derivado aparece neles - `memory/tools.spec.ts` percorre os dois e reprova se um vazar.
|
|
362
363
|
*/
|
|
363
364
|
...MEMORY_TOOLS,
|
|
365
|
+
/**
|
|
366
|
+
* O BLUEPRINT DE PREFERÊNCIAS - o jeito da PESSOA montar um tipo de página, guardado na conta
|
|
367
|
+
* dela (jornada "o blueprint de template", 02/10). Duas ferramentas, e as duas só se chamam
|
|
368
|
+
* quando ELA pede: quem cria sem pedir continua livre ("ela não deve travar o usuário lá na hora
|
|
369
|
+
* que ele estiver na parte criativa dele").
|
|
370
|
+
*/
|
|
371
|
+
{
|
|
372
|
+
name: "preferences",
|
|
373
|
+
description: "The person's OWN preference blueprint for a kind of page - how they like a landing, a blog article or a dashboard built (structure, behavior, view), kept in their account and shared by all their projects. Call this BEFORE building or reshaping a page of one of these kinds - their blueprints are on by default, so a plain \"make me a landing page\" gets them too. Skip it only when the request says to build without them; a project that turned them off answers so itself. The rules name roles and steps; you resolve them to this project's --ds-* variables.",
|
|
374
|
+
inputSchema: {
|
|
375
|
+
type: "object",
|
|
376
|
+
properties: {
|
|
377
|
+
kind: {
|
|
378
|
+
type: "string",
|
|
379
|
+
enum: [...PREFERENCE_KINDS],
|
|
380
|
+
description: "Which kind of page is being built.",
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
required: ["kind"],
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
name: "remember_preference",
|
|
388
|
+
description: 'Keep ONE preference the person just told you about how a kind of page should be built - a correction they made ("the hero needs more room", "I like the menu sliding from the right") or something they asked you to remember. Call it only when THEY say it, never from your own taste. It lands loose in their account; nothing joins a blueprint until they group it on the site. Write it with roles and steps, never values: "the accent role", "the largest spacing step" - a hex or a pixel size is refused, because it would tie the preference to one system.',
|
|
389
|
+
inputSchema: {
|
|
390
|
+
type: "object",
|
|
391
|
+
properties: {
|
|
392
|
+
kind: { type: "string", enum: [...PREFERENCE_KINDS] },
|
|
393
|
+
layer: {
|
|
394
|
+
type: "string",
|
|
395
|
+
enum: [...PREFERENCE_LAYERS],
|
|
396
|
+
description: "structure = sections, room, drawing, what never misses · behavior = motion, transitions, the phone menu · view = focus, parallax, smooth or raw",
|
|
397
|
+
},
|
|
398
|
+
text: {
|
|
399
|
+
type: "string",
|
|
400
|
+
description: "The preference in one sentence, as the person would say it.",
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
required: ["kind", "layer", "text"],
|
|
404
|
+
},
|
|
405
|
+
},
|
|
364
406
|
];
|
|
365
407
|
/**
|
|
366
408
|
* QUANTAS FERRAMENTAS ESTE SERVIDOR SERVE, lido da lista.
|
|
@@ -1890,6 +1932,31 @@ cli) {
|
|
|
1890
1932
|
});
|
|
1891
1933
|
});
|
|
1892
1934
|
}
|
|
1935
|
+
case "preferences": {
|
|
1936
|
+
const kind = args.kind;
|
|
1937
|
+
if (!isKind(kind))
|
|
1938
|
+
return fromContract(`kind is one of ${PREFERENCE_KINDS.join(", ")} - say which kind of page is being built.`);
|
|
1939
|
+
const token = await readToken();
|
|
1940
|
+
if (!token)
|
|
1941
|
+
return fromContract("Not signed in, so the person's preferences cannot be read. They have to run `synthesisui login` once in the terminal - it opens a browser, and I cannot complete it for them.");
|
|
1942
|
+
const prefs = await fetchPreferences({ base: resolveRegistry(), token }, kind);
|
|
1943
|
+
if ("failed" in prefs)
|
|
1944
|
+
return fromContract(`Their preferences could not be read: ${prefs.failed}. Build with the system as usual and say so in one line.`);
|
|
1945
|
+
return preferencesAnswer(kind, prefs, await readSelection(root), await readOff(root));
|
|
1946
|
+
}
|
|
1947
|
+
case "remember_preference": {
|
|
1948
|
+
const { kind, layer } = args;
|
|
1949
|
+
const text = String(args.text ?? "").trim();
|
|
1950
|
+
if (!isKind(kind) || !isLayer(layer) || !text)
|
|
1951
|
+
return fromContract(`Not kept: send kind (${PREFERENCE_KINDS.join(", ")}), layer (${PREFERENCE_LAYERS.join(", ")}) and the text.`);
|
|
1952
|
+
const token = await readToken();
|
|
1953
|
+
if (!token)
|
|
1954
|
+
return fromContract("Not signed in, so nothing can be kept. They have to run `synthesisui login` once in the terminal.");
|
|
1955
|
+
const out = await sendPiece({ base: resolveRegistry(), token }, { kind, layer, text });
|
|
1956
|
+
return out.ok
|
|
1957
|
+
? fromContract(`Kept, loose, in their ${kind} preferences: "${text}". Tell them in one line that they can group it into a blueprint under Memory -> Preference blueprints on the site.`)
|
|
1958
|
+
: fromContract(`Not kept: ${out.message}`);
|
|
1959
|
+
}
|
|
1893
1960
|
case "recall": {
|
|
1894
1961
|
const reach = await memoryReach(root);
|
|
1895
1962
|
if (typeof reach === "string")
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { readToken, resolveRegistry } from "../config.js";
|
|
2
|
+
import { chosen, describeBlueprint, fetchPreferences, readSelection, selectionPath, writeOff, writeSelection, } from "../preferences.js";
|
|
3
|
+
/**
|
|
4
|
+
* `synthesisui preferences` - os blueprints de preferências da pessoa, e quais ESTE projeto usa.
|
|
5
|
+
*
|
|
6
|
+
* preferences lista os dela, marcando os que este projeto usa
|
|
7
|
+
* preferences use <nome>... este projeto passa a usar só esses (nome ou id)
|
|
8
|
+
* preferences use --all volta a usar todos os dela (apaga o arquivo do projeto)
|
|
9
|
+
* preferences off | on desliga ou liga os blueprints neste projeto (ligados por padrão desde 03/10)
|
|
10
|
+
*
|
|
11
|
+
* O blueprint mora na conta dela; o projeto só guarda o recorte, em `_synthesisui/preferences.json`,
|
|
12
|
+
* versionado - escolher quais preferências um projeto segue é decisão do projeto, e viaja com ele.
|
|
13
|
+
*/
|
|
14
|
+
export async function preferences(opts) {
|
|
15
|
+
const root = opts.dir ?? process.cwd();
|
|
16
|
+
const token = await readToken();
|
|
17
|
+
if (!token) {
|
|
18
|
+
console.error("Not signed in - run `synthesisui login` first. Your preference blueprints live in your account.");
|
|
19
|
+
process.exitCode = 1;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const prefs = await fetchPreferences({
|
|
23
|
+
base: resolveRegistry(opts.registry),
|
|
24
|
+
token,
|
|
25
|
+
});
|
|
26
|
+
if ("failed" in prefs) {
|
|
27
|
+
console.error(`Your preferences could not be read: ${prefs.failed}.`);
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const [sub, ...names] = opts.args;
|
|
32
|
+
if (sub === "off" || sub === "on") {
|
|
33
|
+
await writeOff(root, sub === "off");
|
|
34
|
+
console.log(sub === "off"
|
|
35
|
+
? "Your blueprints are off for this project - the agent builds with the system as usual. `synthesisui preferences on` turns them back on."
|
|
36
|
+
: "Your blueprints are on for this project - the agent reads them before building a page.");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (sub === "use") {
|
|
40
|
+
if (opts.all) {
|
|
41
|
+
await writeSelection(root, null);
|
|
42
|
+
console.log(`This project now uses all ${prefs.blueprints.length} of your preference blueprints.`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (names.length === 0) {
|
|
46
|
+
console.error("Say which: `synthesisui preferences use <name>...`, or `--all` for every one of yours.");
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const found = chosen(prefs.blueprints, names);
|
|
51
|
+
const missing = names.filter((n) => !found.some((b) => b.name.toLowerCase() === n.toLowerCase() || b.id === n));
|
|
52
|
+
if (missing.length > 0) {
|
|
53
|
+
console.error(`Not yours: ${missing.map((m) => `"${m}"`).join(", ")}. Yours are: ${prefs.blueprints.map((b) => `"${b.name}"`).join(", ") || "none yet"}.`);
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
await writeSelection(root, found.map((b) => b.name));
|
|
58
|
+
console.log(`This project now uses ${found.map((b) => `"${b.name}" (${b.kind})`).join(", ")} - written to ${selectionPath(root)}.`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const selection = await readSelection(root);
|
|
62
|
+
const using = new Set(chosen(prefs.blueprints, selection).map((b) => b.id));
|
|
63
|
+
if (prefs.blueprints.length === 0) {
|
|
64
|
+
console.log("No preference blueprints yet. Keep them on the site, under Memory -> Preference blueprints - or ask your agent to remember a correction you make.");
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.log(selection === null
|
|
68
|
+
? "Your preference blueprints - this project uses all of them:"
|
|
69
|
+
: "Your preference blueprints - [x] is what this project uses:");
|
|
70
|
+
for (const b of prefs.blueprints)
|
|
71
|
+
console.log(`\n[${using.has(b.id) ? "x" : " "}] ${describeBlueprint(b)}`);
|
|
72
|
+
}
|
|
73
|
+
if (prefs.loose.length > 0)
|
|
74
|
+
console.log(`\n${prefs.loose.length} loose piece(s) waiting to be grouped on the site.`);
|
|
75
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { readProjectTheme, writeNewProjectRoot, writeNewProjectTheme, } from "./new-project-theme.js";
|
|
4
|
+
import { resolveDeps, tailwindMajor } from "./stack.js";
|
|
5
|
+
/**
|
|
6
|
+
* O TEMA LIGADO NA INSTALAÇÃO - a jornada "a conta nasce com a base" (03/10).
|
|
7
|
+
*
|
|
8
|
+
* No teste final o pedido simples parou antes de montar a página: "As regras do projeto me proíbem de mexer no tema sem
|
|
9
|
+
* você pedir". O `init --ds` já ligava o tema de um projeto novo; o `add`, que o site também manda rodar, não. Ele,
|
|
10
|
+
* 16:39: "liga o tema sozinho na primeira página" - instalar o sistema já é o sim.
|
|
11
|
+
*
|
|
12
|
+
* A trava é a mesma do `init`: SÓ o projeto sem vocabulário próprio. Projeto que já nomeia os tokens dele não recebe
|
|
13
|
+
* nada - "os outros, eles têm que se comportar de acordo com o padrão do próprio projeto do usuário" (27/09).
|
|
14
|
+
*
|
|
15
|
+
* Tailwind v4 os nomes do sistema no @theme dele, com os valores (writeNewProjectTheme)
|
|
16
|
+
* sem Tailwind os nomes no :root dele (writeNewProjectRoot), que já recusa um projeto com nomes próprios
|
|
17
|
+
*/
|
|
18
|
+
export async function wireTheme(root, ds) {
|
|
19
|
+
const slugDir = join(root, "_synthesisui", "ds", ds);
|
|
20
|
+
const lock = JSON.parse(await readFile(join(slugDir, ".lock"), "utf8").catch(() => "{}"));
|
|
21
|
+
const version = typeof lock.version === "number" ? lock.version : null;
|
|
22
|
+
if (version === null)
|
|
23
|
+
return { written: null, count: 0, where: null };
|
|
24
|
+
const tokens = await readFile(join(slugDir, `v${version}`, "tokens.css"), "utf8").catch(() => "");
|
|
25
|
+
if (!tokens)
|
|
26
|
+
return { written: null, count: 0, where: null };
|
|
27
|
+
const deps = await resolveDeps(root).catch(() => ({}));
|
|
28
|
+
if (deps.tailwindcss) {
|
|
29
|
+
const major = tailwindMajor(deps);
|
|
30
|
+
if (major === null || major < 4)
|
|
31
|
+
return { written: null, count: 0, where: null };
|
|
32
|
+
const plan = await readProjectTheme(root, tokens, ds);
|
|
33
|
+
if (plan?.kind !== "new")
|
|
34
|
+
return { written: null, count: 0, where: null };
|
|
35
|
+
const t = await writeNewProjectTheme(root, tokens, ds, version);
|
|
36
|
+
return {
|
|
37
|
+
written: t.written,
|
|
38
|
+
count: t.count,
|
|
39
|
+
where: t.written ? "@theme" : null,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const t = await writeNewProjectRoot(root, tokens, ds, version);
|
|
43
|
+
return {
|
|
44
|
+
written: t.written,
|
|
45
|
+
count: t.count,
|
|
46
|
+
where: t.written ? ":root" : null,
|
|
47
|
+
};
|
|
48
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { align } from "./commands/align.js";
|
|
|
11
11
|
import { autopilot } from "./commands/autopilot.js";
|
|
12
12
|
import { ci } from "./commands/ci.js";
|
|
13
13
|
import { clean } from "./commands/clean.js";
|
|
14
|
+
import { colours } from "./commands/colours.js";
|
|
14
15
|
import { component } from "./commands/component.js";
|
|
15
16
|
import { connect } from "./commands/connect.js";
|
|
16
17
|
import { doctor } from "./commands/doctor.js";
|
|
@@ -24,6 +25,7 @@ import { login } from "./commands/login.js";
|
|
|
24
25
|
import { logout } from "./commands/logout.js";
|
|
25
26
|
import { mcp } from "./commands/mcp.js";
|
|
26
27
|
import { mode } from "./commands/mode.js";
|
|
28
|
+
import { preferences } from "./commands/preferences.js";
|
|
27
29
|
import { refit } from "./commands/refit.js";
|
|
28
30
|
import { request } from "./commands/request.js";
|
|
29
31
|
import { status } from "./commands/status.js";
|
|
@@ -31,7 +33,6 @@ import { summary } from "./commands/summary.js";
|
|
|
31
33
|
import { sync } from "./commands/sync.js";
|
|
32
34
|
import { template } from "./commands/template.js";
|
|
33
35
|
import { upgrade } from "./commands/upgrade.js";
|
|
34
|
-
import { colours } from "./commands/colours.js";
|
|
35
36
|
import { use } from "./commands/use.js";
|
|
36
37
|
import { appendEvent } from "./doctor/ledger.js";
|
|
37
38
|
import { blueprintTarget, installedSlugs } from "./installed.js";
|
|
@@ -87,6 +88,10 @@ Usage - governance (deterministic, FREE):
|
|
|
87
88
|
the measurement, --yes skips the overwrite question)
|
|
88
89
|
synthesisui doctor --fix [--write] show every hand-written value your system already names,
|
|
89
90
|
and the token for it. --write applies them in place
|
|
91
|
+
synthesisui preferences your preference blueprints (how YOU build a landing, a blog,
|
|
92
|
+
a dashboard) and which ones this project uses
|
|
93
|
+
synthesisui preferences use <name>... this project follows only those (--all: every one of yours)
|
|
94
|
+
synthesisui preferences off | on turn your blueprints off or on for this project (on by default)
|
|
90
95
|
synthesisui status where this project stands: installed version, last check,
|
|
91
96
|
the ratchet, open requests - local first, no login needed
|
|
92
97
|
synthesisui ci [--write] the two steps that put drift in your PR - annotations on
|
|
@@ -438,6 +443,10 @@ async function main() {
|
|
|
438
443
|
});
|
|
439
444
|
break;
|
|
440
445
|
}
|
|
446
|
+
case "preferences":
|
|
447
|
+
/** Os blueprints de preferências dela - ver `commands/preferences.ts`. */
|
|
448
|
+
await preferences({ dir, registry, args, all: flags.all === true });
|
|
449
|
+
break;
|
|
441
450
|
case "status":
|
|
442
451
|
/** A resposta do dia a dia no terminal - ver `commands/status.ts`. Local primeiro. */
|
|
443
452
|
await status({ dir, registry, cli: CLI_VERSION });
|
|
@@ -542,7 +551,9 @@ async function main() {
|
|
|
542
551
|
componentsDir,
|
|
543
552
|
styles,
|
|
544
553
|
ds,
|
|
545
|
-
...(typeof flags["theme-in"] === "string"
|
|
554
|
+
...(typeof flags["theme-in"] === "string"
|
|
555
|
+
? { themeIn: flags["theme-in"] }
|
|
556
|
+
: {}),
|
|
546
557
|
cli: CLI_VERSION,
|
|
547
558
|
});
|
|
548
559
|
break;
|
package/dist/install-marks.js
CHANGED
|
@@ -314,7 +314,12 @@
|
|
|
314
314
|
* 0.16.483 (29/09): o `.lock` sai sem a hora (`fetchedAt` vai para `.fetched-at.json`, ignorado pelo git) - dois
|
|
315
315
|
* upgrades da mesma versão, em branches separadas, davam conflito numa linha só dele. Ver `installed-at.ts`.
|
|
316
316
|
*/
|
|
317
|
-
|
|
317
|
+
/**
|
|
318
|
+
* 0.16.483 -> 0.16.488 em 02/10, e o passo 1 dá **SIM** (o blueprint de template, E05): o bloco do `CLAUDE.md` ganha a
|
|
319
|
+
* linha das preferências da pessoa - `preferences` quando ela pede, `remember_preference` quando ela corrige. Uma pasta
|
|
320
|
+
* escrita antes não diz ao agente que essas portas existem.
|
|
321
|
+
*/
|
|
322
|
+
export const MATERIALISER_SINCE = "0.16.488";
|
|
318
323
|
/**
|
|
319
324
|
* A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
|
|
320
325
|
*
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* O BLUEPRINT DE PREFERÊNCIAS, DO LADO DO AGENTE - o jeito da PESSOA montar uma landing, um blog ou
|
|
5
|
+
* um dashboard, guardado na conta dela e não no sistema.
|
|
6
|
+
*
|
|
7
|
+
* Três regras, e todas vêm do pedido dele (02/10):
|
|
8
|
+
*
|
|
9
|
+
* SÓ QUANDO ELA PEDE "ela não deve travar o usuário lá na hora que ele estiver na parte
|
|
10
|
+
* criativa dele" - o agente lê o blueprint quando o pedido diz para usar as
|
|
11
|
+
* preferências, nunca em todo turno
|
|
12
|
+
* PAPÉIS, NÃO VALORES o blueprint fala de "o destaque" e "o maior passo de espaço"; quem vira cor
|
|
13
|
+
* é o sistema DESTE projeto, pelas --ds-*
|
|
14
|
+
* O PROJETO ESCOLHE `_synthesisui/preferences.json` diz quais blueprints dela este projeto usa.
|
|
15
|
+
* Sem o arquivo, valem todos os dela daquele tipo
|
|
16
|
+
*/
|
|
17
|
+
export const PREFERENCE_KINDS = ["landing", "blog", "dashboard"];
|
|
18
|
+
export const PREFERENCE_LAYERS = ["structure", "behavior", "view"];
|
|
19
|
+
export const isKind = (k) => typeof k === "string" && PREFERENCE_KINDS.includes(k);
|
|
20
|
+
export const isLayer = (l) => typeof l === "string" && PREFERENCE_LAYERS.includes(l);
|
|
21
|
+
/** Onde o projeto guarda quais blueprints dela ele usa - versionado, porque é decisão do projeto. */
|
|
22
|
+
export const selectionPath = (root) => join(root, "_synthesisui", "preferences.json");
|
|
23
|
+
export async function readSelection(root) {
|
|
24
|
+
const raw = await readFile(selectionPath(root), "utf8").catch(() => null);
|
|
25
|
+
if (raw === null)
|
|
26
|
+
return null;
|
|
27
|
+
try {
|
|
28
|
+
const j = JSON.parse(raw);
|
|
29
|
+
return Array.isArray(j.use) ? j.use.map(String) : null;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* DESLIGAR NO PROJETO (03/10) - os blueprints ficam ligados por padrão, "e desativado se a gente precisar". O mesmo
|
|
37
|
+
* arquivo do recorte guarda `off: true`; ligar de novo tira a chave e mantém o recorte.
|
|
38
|
+
*/
|
|
39
|
+
export async function readOff(root) {
|
|
40
|
+
const raw = await readFile(selectionPath(root), "utf8").catch(() => null);
|
|
41
|
+
if (raw === null)
|
|
42
|
+
return false;
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(raw).off === true;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export async function writeOff(root, off) {
|
|
51
|
+
const raw = await readFile(selectionPath(root), "utf8").catch(() => null);
|
|
52
|
+
let j = {};
|
|
53
|
+
try {
|
|
54
|
+
j = raw ? JSON.parse(raw) : {};
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
j = {};
|
|
58
|
+
}
|
|
59
|
+
if (off)
|
|
60
|
+
j.off = true;
|
|
61
|
+
else
|
|
62
|
+
delete j.off;
|
|
63
|
+
if (Object.keys(j).length === 0) {
|
|
64
|
+
await rm(selectionPath(root), { force: true });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
await mkdir(join(root, "_synthesisui"), { recursive: true });
|
|
68
|
+
await writeFile(selectionPath(root), `${JSON.stringify(j, null, 2)}\n`);
|
|
69
|
+
}
|
|
70
|
+
export async function writeSelection(root, names) {
|
|
71
|
+
if (names === null) {
|
|
72
|
+
await rm(selectionPath(root), { force: true });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
await mkdir(join(root, "_synthesisui"), { recursive: true });
|
|
76
|
+
const off = await readOff(root);
|
|
77
|
+
await writeFile(selectionPath(root), `${JSON.stringify(off ? { use: names, off } : { use: names }, null, 2)}\n`);
|
|
78
|
+
}
|
|
79
|
+
/** Os blueprints que este projeto usa: o recorte do arquivo, ou todos os dela. Nome ou id servem. */
|
|
80
|
+
export function chosen(all, selection) {
|
|
81
|
+
if (selection === null)
|
|
82
|
+
return all;
|
|
83
|
+
const want = new Set(selection.map((s) => s.toLowerCase()));
|
|
84
|
+
return all.filter((b) => want.has(b.name.toLowerCase()) || want.has(b.id.toLowerCase()));
|
|
85
|
+
}
|
|
86
|
+
export async function fetchPreferences(reach, kind) {
|
|
87
|
+
const res = await fetch(`${reach.base}/api/preferences${kind ? `?kind=${kind}` : ""}`, { headers: { Authorization: `Bearer ${reach.token}` } }).catch(() => null);
|
|
88
|
+
if (!res)
|
|
89
|
+
return { failed: "the platform could not be reached" };
|
|
90
|
+
if (!res.ok)
|
|
91
|
+
return { failed: `the platform answered ${res.status}` };
|
|
92
|
+
const j = (await res.json().catch(() => null));
|
|
93
|
+
return j ?? { failed: "the platform answered something unreadable" };
|
|
94
|
+
}
|
|
95
|
+
export async function sendPiece(reach, piece) {
|
|
96
|
+
const res = await fetch(`${reach.base}/api/preferences/pieces`, {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: {
|
|
99
|
+
"content-type": "application/json",
|
|
100
|
+
Authorization: `Bearer ${reach.token}`,
|
|
101
|
+
},
|
|
102
|
+
body: JSON.stringify({ ...piece, origin: "correction" }),
|
|
103
|
+
}).catch(() => null);
|
|
104
|
+
if (!res)
|
|
105
|
+
return { ok: false, message: "the platform could not be reached" };
|
|
106
|
+
const j = (await res.json().catch(() => null));
|
|
107
|
+
if (!res.ok)
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
message: j?.message ?? `the platform answered ${res.status}`,
|
|
111
|
+
};
|
|
112
|
+
return { ok: true, id: String(j?.id ?? "") };
|
|
113
|
+
}
|
|
114
|
+
const LAYER_TITLE = {
|
|
115
|
+
structure: "STRUCTURE",
|
|
116
|
+
behavior: "BEHAVIOR",
|
|
117
|
+
view: "VIEW",
|
|
118
|
+
};
|
|
119
|
+
/** As escolhas de guia em palavras - o agente não tem guia, então elas viram instrução. */
|
|
120
|
+
function choicesLines(c) {
|
|
121
|
+
const out = [];
|
|
122
|
+
if (c.density)
|
|
123
|
+
out.push(`section rhythm: ${c.density}`);
|
|
124
|
+
if (c.transitions)
|
|
125
|
+
out.push(`between sections: ${c.transitions}`);
|
|
126
|
+
if (c.icons === "custom")
|
|
127
|
+
out.push("icons: their own inline SVG drawings, painted with the system's colour roles");
|
|
128
|
+
const nav = (c.navbar ?? {});
|
|
129
|
+
if (nav.mobile)
|
|
130
|
+
out.push(`phone menu: ${nav.mobile}`);
|
|
131
|
+
if (nav.sticky !== undefined)
|
|
132
|
+
out.push(nav.sticky
|
|
133
|
+
? "navigation sticks on scroll"
|
|
134
|
+
: "navigation stays solid in the flow");
|
|
135
|
+
if (c.heroMotion)
|
|
136
|
+
out.push(`hero motion: ${c.heroMotion}`);
|
|
137
|
+
if (c.sectionMotion)
|
|
138
|
+
out.push(`section motion: ${c.sectionMotion}`);
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
export function describeBlueprint(b) {
|
|
142
|
+
const groups = PREFERENCE_LAYERS.map((l) => ({
|
|
143
|
+
l,
|
|
144
|
+
rules: b.pieces.filter((p) => p.layer === l),
|
|
145
|
+
})).filter((g) => g.rules.length > 0);
|
|
146
|
+
const choices = choicesLines(b.guideChoices);
|
|
147
|
+
return [
|
|
148
|
+
`"${b.name}" (${b.kind})`,
|
|
149
|
+
...groups.flatMap((g) => [
|
|
150
|
+
` ${LAYER_TITLE[g.l]}:`,
|
|
151
|
+
...g.rules.map((r) => ` - ${r.text}`),
|
|
152
|
+
]),
|
|
153
|
+
...(choices.length > 0
|
|
154
|
+
? [" THEIR USUAL CHOICES:", ...choices.map((c) => ` - ${c}`)]
|
|
155
|
+
: []),
|
|
156
|
+
].join("\n");
|
|
157
|
+
}
|
|
158
|
+
/** A linha de procedência é o servidor quem escreve, a partir de `source` - aqui ela sairia duas vezes. */
|
|
159
|
+
const composed = (body, detail) => ({
|
|
160
|
+
body,
|
|
161
|
+
source: { kind: "composed", detail },
|
|
162
|
+
});
|
|
163
|
+
/** O que a plataforma sabe de montar ESTE tipo - vem depois dos blueprints dela, que ganham dele quando discordam. */
|
|
164
|
+
function craftOf(kind, prefs) {
|
|
165
|
+
const lines = prefs.craft?.[kind] ?? [];
|
|
166
|
+
if (lines.length === 0)
|
|
167
|
+
return [];
|
|
168
|
+
return [
|
|
169
|
+
"",
|
|
170
|
+
`HOW THIS PLATFORM BUILDS A ${kind.toUpperCase()} - the same rules its guide uses; their blueprint wins where the two disagree:`,
|
|
171
|
+
...lines.map((l) => `- ${l}`),
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
/** A resposta da ferramenta `preferences` - lida SÓ quando o pedido diz para usar as preferências dela. */
|
|
175
|
+
export function preferencesAnswer(kind, prefs, selection,
|
|
176
|
+
/** O projeto desligou os blueprints (`synthesisui preferences off`). */
|
|
177
|
+
off = false) {
|
|
178
|
+
if (off)
|
|
179
|
+
return composed(`This project turned the person's blueprints off (_synthesisui/preferences.json). Build the ${kind} with the system as usual; they turn them back on with \`synthesisui preferences on\`.`, `preferences:${kind}:off`);
|
|
180
|
+
const mine = prefs.blueprints.filter((b) => b.kind === kind);
|
|
181
|
+
const use = chosen(mine, selection);
|
|
182
|
+
if (use.length === 0) {
|
|
183
|
+
const why = mine.length === 0
|
|
184
|
+
? `The person has no ${kind} preference blueprint yet.`
|
|
185
|
+
: `The person has ${mine.length} ${kind} blueprint(s) (${mine.map((b) => `"${b.name}"`).join(", ")}), but this project chose others in _synthesisui/preferences.json.`;
|
|
186
|
+
// não ter preferência não é lacuna do sistema: é uma resposta verdadeira, e não entra na conta de lacunas
|
|
187
|
+
return composed(`${why} Build the ${kind} with the system as usual, and tell them in one line that they can keep their ${kind} preferences under Memory -> Preference blueprints on the site.`, `preferences:${kind}:0`);
|
|
188
|
+
}
|
|
189
|
+
return composed([
|
|
190
|
+
`The person's preference blueprint${use.length > 1 ? "s" : ""} for a ${kind} - THEIR preferences, kept by them across projects:`,
|
|
191
|
+
"",
|
|
192
|
+
...use.map(describeBlueprint),
|
|
193
|
+
"",
|
|
194
|
+
"How to apply them:",
|
|
195
|
+
"- They name ROLES and STEPS, never values. Resolve every role to THIS project's --ds-* variables (the accent role is --ds-color-semantic-accent here, the largest spacing step is the system's largest --ds-spacing-*). Drawing is welcome: an inline SVG painted with fill/stroke on var(--ds-color-...) is the system's vocabulary, not drift.",
|
|
196
|
+
"- They win over generic page craft when the two disagree; this system's tokens and rules still win over them.",
|
|
197
|
+
`- When you finish, tell them in ONE line which blueprint you applied${use.length > 1 ? " (if more than one fits, use the first and name it)" : ""}, and any rule you could not follow and why.`,
|
|
198
|
+
...craftOf(kind, prefs),
|
|
199
|
+
].join("\n"), `preferences:${kind}:${use.length}`);
|
|
200
|
+
}
|
package/package.json
CHANGED