synthesisui 0.16.162 → 0.16.164
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/add.js +68 -28
- package/dist/commands/align.js +37 -0
- package/dist/commands/doctor.js +21 -0
- package/dist/commands/mcp.js +96 -0
- package/dist/commands/use.js +10 -8
- package/dist/guide.js +15 -8
- package/package.json +1 -1
package/dist/commands/add.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { syncClaudeMd } from "../claude-md.js";
|
|
4
4
|
import { readProjectConfig, resolveRegistry } from "../config.js";
|
|
@@ -41,6 +41,33 @@ async function writeGovernanceIgnore(projectRoot) {
|
|
|
41
41
|
"ledger.jsonl\n" +
|
|
42
42
|
"not-expressed.md\n", "utf8");
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* APAGA A PROSA QUE ESTA VERSÃO DEIXOU DE ESCREVER.
|
|
46
|
+
*
|
|
47
|
+
* `rules.md` e `philosophy.md` eram materializados só pelo `add`, então quem instalou antes disto
|
|
48
|
+
* tem dois arquivos que nada mais atualiza - e um deles se apresenta como "Max authority - follow
|
|
49
|
+
* these first". Um arquivo que afirma autoridade máxima sobre a regra de uma versão qualquer do
|
|
50
|
+
* passado é pior que arquivo nenhum, e deixá-lo lá esperando alguém lembrar de apagar é exatamente
|
|
51
|
+
* a administração manual que esta esteira existe para não pedir (dono, 06/08).
|
|
52
|
+
*
|
|
53
|
+
* É a NOSSA pasta - o `.gitignore` que este mesmo comando escreve já a declara gerenciada -, então
|
|
54
|
+
* isto não toca em código dele. E é dito na saída, porque apagar em silêncio é a outra metade do
|
|
55
|
+
* mesmo erro.
|
|
56
|
+
*/
|
|
57
|
+
async function retireMaterializedDoctrine(slugDir) {
|
|
58
|
+
const gone = [];
|
|
59
|
+
for (const name of ["rules.md", "philosophy.md"]) {
|
|
60
|
+
const path = join(slugDir, name);
|
|
61
|
+
try {
|
|
62
|
+
await access(path);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
await rm(path).then(() => gone.push(name), () => { });
|
|
68
|
+
}
|
|
69
|
+
return gone;
|
|
70
|
+
}
|
|
44
71
|
async function readRootLock(path) {
|
|
45
72
|
try {
|
|
46
73
|
return JSON.parse(await readFile(path, "utf8"));
|
|
@@ -114,7 +141,8 @@ export async function add(slug, opts) {
|
|
|
114
141
|
};
|
|
115
142
|
await writeFile(rootLockPath, `${JSON.stringify(lock, null, 2)}\n`, "utf8");
|
|
116
143
|
await writeGovernanceIgnore(projectRoot);
|
|
117
|
-
|
|
144
|
+
const retired = await retireMaterializedDoctrine(slugDir);
|
|
145
|
+
// 5b. governance rules (personal DS) → doctrine.json at the slug root (stable path,
|
|
118
146
|
// highest authority; the GUIDE tells the agent to read it first)
|
|
119
147
|
/**
|
|
120
148
|
* The structured set wins when the registry sent one, because only this side
|
|
@@ -129,13 +157,21 @@ export async function add(slug, opts) {
|
|
|
129
157
|
const leftOut = payload.ruleSet
|
|
130
158
|
? describeFiltered(payload.ruleSet, stack)
|
|
131
159
|
: null;
|
|
160
|
+
/**
|
|
161
|
+
* AS REGRAS VIAJAM COMO DADO, e não como prosa materializada.
|
|
162
|
+
*
|
|
163
|
+
* `rules.md` e `philosophy.md` eram escritos SÓ pelo `add` (e o `upgrade` chama o `add`), então
|
|
164
|
+
* entre um upgrade e outro eles eram uma cópia velha de um dado que é VERSIONADO do nosso lado - e
|
|
165
|
+
* o GUIDE chamava um deles de "maximum authority". Uma autoridade máxima que envelhece em silêncio
|
|
166
|
+
* é pior que nenhuma (dono, 06/08).
|
|
167
|
+
*
|
|
168
|
+
* O conteúdo não sai do disco, porque o agente precisa dele offline e sem aprovar servidor nenhum.
|
|
169
|
+
* O que muda é a FORMA e quem o apresenta: aqui fica o dado pinado na versão instalada, e o
|
|
170
|
+
* `system_doctrine` do MCP é quem o serve - dizendo qual versão ele está lendo e se existe uma mais
|
|
171
|
+
* nova, em vez de deixar a pessoa descobrir pela ausência.
|
|
172
|
+
*/
|
|
132
173
|
if (rules.length > 0) {
|
|
133
|
-
|
|
134
|
-
"> Accumulated rules for this design system. **Max authority - follow these first.**\n" +
|
|
135
|
-
`> Managed by synthesisui (edit in the studio). ${rules.length} rule(s).\n\n${rules
|
|
136
|
-
.map((r) => `- ${r}`)
|
|
137
|
-
.join("\n")}\n`;
|
|
138
|
-
await writeFile(join(slugDir, "rules.md"), body, "utf8");
|
|
174
|
+
await writeFile(join(slugDir, "doctrine.json"), `${JSON.stringify({ version: payload.version, name: payload.name, rules }, null, 2)}\n`, "utf8");
|
|
139
175
|
}
|
|
140
176
|
// NO SILENT DROPS. A rule removed because it belongs to another environment is
|
|
141
177
|
// a thing the person should hear once, not discover by its absence.
|
|
@@ -163,22 +199,17 @@ export async function add(slug, opts) {
|
|
|
163
199
|
if (required.length > 0) {
|
|
164
200
|
await writeFile(join(slugDir, "requires.json"), `${JSON.stringify(required, null, 2)}\n`, "utf8");
|
|
165
201
|
}
|
|
166
|
-
|
|
167
|
-
// Narrative guidance (mission, principles, voice, motion doctrine…); the
|
|
168
|
-
// GUIDE points the agent here right after rules.md.
|
|
202
|
+
/** A filosofia continua sendo CONTADA na saída, e ela vive no documento - ver abaixo. */
|
|
169
203
|
const philosophy = payload.document.philosophy;
|
|
170
204
|
const sections = philosophy?.sections ?? [];
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
await writeFile(join(slugDir, "philosophy.md"), `${parts.join("\n")}\n`, "utf8");
|
|
181
|
-
}
|
|
205
|
+
/**
|
|
206
|
+
* 5c. A FILOSOFIA NÃO É MATERIALIZADA - ela já está aqui.
|
|
207
|
+
*
|
|
208
|
+
* `philosophy` é campo do DOCUMENTO versionado (`design-system.ts`), e o `design-system.json`
|
|
209
|
+
* escrito no passo 2 a carrega inteira. O `philosophy.md` era uma segunda cópia do mesmo dado, no
|
|
210
|
+
* mesmo commit, que só era reescrita no upgrade seguinte - dois caminhos para uma verdade é como
|
|
211
|
+
* um deles começa a discordar do outro. O `system_doctrine` lê do documento.
|
|
212
|
+
*/
|
|
182
213
|
// 6. discovery by the agent
|
|
183
214
|
const claudeMd = await syncClaudeMd(projectRoot);
|
|
184
215
|
// outcome line
|
|
@@ -201,12 +232,21 @@ export async function add(slug, opts) {
|
|
|
201
232
|
"GUIDE.md",
|
|
202
233
|
];
|
|
203
234
|
console.log(` v${v}/: ${files.join(", ")}`);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
235
|
+
/**
|
|
236
|
+
* O QUE O AGENTE VAI LER, e por qual porta - nomear o arquivo quando a porta é uma ferramenta
|
|
237
|
+
* mandaria alguém procurar um caminho que não existe mais.
|
|
238
|
+
*/
|
|
239
|
+
const doctrine = [
|
|
240
|
+
...(rules.length > 0 ? [`${rules.length} rule(s)`] : []),
|
|
241
|
+
...(sections.length > 0 || philosophy?.context
|
|
242
|
+
? [`${sections.length} section(s) of philosophy`]
|
|
243
|
+
: []),
|
|
244
|
+
];
|
|
245
|
+
if (doctrine.length > 0)
|
|
246
|
+
console.log(` ${doctrine.join(" and ")} → your agent reads them with \`system_doctrine\`, pinned to v${v}`);
|
|
247
|
+
/** Nomeado, nunca em silêncio: apagar arquivo no repo de alguém se diz em voz alta. */
|
|
248
|
+
if (retired.length > 0)
|
|
249
|
+
console.log(` removed ${retired.join(" and ")} - nothing rewrote them after an install, so they stated an older version's rules as current`);
|
|
210
250
|
console.log(` CLAUDE.md ${claudeMd.created ? "created" : "updated"} (${claudeMd.count} system(s) installed)`);
|
|
211
251
|
if (opts.setupHints === false)
|
|
212
252
|
return;
|
package/dist/commands/align.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
+
import { readToken, resolveRegistry } from "../config.js";
|
|
4
5
|
import { readEvents } from "../doctor/ledger.js";
|
|
5
6
|
import { measuredScope } from "../measured-scope.js";
|
|
6
7
|
async function locksIn(root) {
|
|
@@ -100,6 +101,38 @@ opts = {}) {
|
|
|
100
101
|
});
|
|
101
102
|
return out;
|
|
102
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* A VERSÃO PUBLICADA CONTRA A INSTALADA - o estado que nenhuma verificação cobria.
|
|
106
|
+
*
|
|
107
|
+
* É o que faz disto um produto de TIME e não de uma pessoa: alguém edita o sistema no estúdio e
|
|
108
|
+
* publica, e o repo de todo mundo continua com o CSS e as regras da versão anterior, sem nada dizer.
|
|
109
|
+
* A doutrina que o agente lê é a da versão instalada de propósito (ver `system_doctrine`), então as
|
|
110
|
+
* duas metades andam juntas - e é justamente por isso que a defasagem precisa ser DITA.
|
|
111
|
+
*
|
|
112
|
+
* Melhor esforço: offline, sem sessão, ou registry fora do ar não produzem linha nenhuma. Uma
|
|
113
|
+
* verificação de abertura não pode transformar um voo sem internet num aviso.
|
|
114
|
+
*/
|
|
115
|
+
export async function versionBehind(root, opts = {}) {
|
|
116
|
+
const locks = (await locksIn(root)).filter((l) => l.slug && !l.adopted);
|
|
117
|
+
const lock = locks[0];
|
|
118
|
+
if (!lock?.slug || lock.version == null)
|
|
119
|
+
return null;
|
|
120
|
+
if (!(await credentials(opts.home ?? homedir())))
|
|
121
|
+
return null;
|
|
122
|
+
const token = await readToken();
|
|
123
|
+
if (!token)
|
|
124
|
+
return null;
|
|
125
|
+
const res = await fetch(`${lock.registry ?? resolveRegistry()}/api/registry/ds/${lock.slug}`, { headers: { Authorization: `Bearer ${token}` } }).catch(() => null);
|
|
126
|
+
if (!res?.ok)
|
|
127
|
+
return null;
|
|
128
|
+
const body = (await res.json().catch(() => null));
|
|
129
|
+
if (!body?.version || body.version <= lock.version)
|
|
130
|
+
return null;
|
|
131
|
+
return {
|
|
132
|
+
says: `v${body.version} of "${lock.slug}" is published and this repo is on v${lock.version}. The CSS here and the rules your agent reads are both v${lock.version} - they move together, which is why this is worth saying rather than applying.`,
|
|
133
|
+
run: `npx synthesisui upgrade ${lock.slug}`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
103
136
|
/**
|
|
104
137
|
* A linha que a sessão vê.
|
|
105
138
|
*
|
|
@@ -127,6 +160,10 @@ export async function align(opts) {
|
|
|
127
160
|
const items = await localMisalignments(root, {
|
|
128
161
|
...(opts.cli ? { cli: opts.cli } : {}),
|
|
129
162
|
}).catch(() => []);
|
|
163
|
+
/** A única linha que custa rede, e ela some inteira quando não há rede. */
|
|
164
|
+
const remote = await versionBehind(root).catch(() => null);
|
|
165
|
+
if (remote)
|
|
166
|
+
items.push(remote);
|
|
130
167
|
const text = describeMisalignments(items);
|
|
131
168
|
if (text)
|
|
132
169
|
console.log(text);
|
package/dist/commands/doctor.js
CHANGED
|
@@ -144,6 +144,7 @@ export async function loadSystem(root) {
|
|
|
144
144
|
recipes: new Map(),
|
|
145
145
|
documents: [],
|
|
146
146
|
requires: [],
|
|
147
|
+
doctrines: [],
|
|
147
148
|
};
|
|
148
149
|
}
|
|
149
150
|
// Several systems can live side by side; every token is prefixed --ds-, so
|
|
@@ -154,6 +155,7 @@ export async function loadSystem(root) {
|
|
|
154
155
|
const recipes = new Map();
|
|
155
156
|
const documents = [];
|
|
156
157
|
const requires = [];
|
|
158
|
+
const doctrines = [];
|
|
157
159
|
for (const slug of slugs) {
|
|
158
160
|
const dir = join(dsDir, slug);
|
|
159
161
|
const raw = await readFile(join(dir, ".lock"), "utf8").catch(() => "");
|
|
@@ -226,12 +228,31 @@ export async function loadSystem(root) {
|
|
|
226
228
|
// Unreadable costs this check, not the run.
|
|
227
229
|
}
|
|
228
230
|
}
|
|
231
|
+
/** Ausente é o caso normal: um sistema sem regra nenhuma não deixa arquivo. */
|
|
232
|
+
const doctrineRaw = await readFile(join(dir, "doctrine.json"), "utf8").catch(() => "");
|
|
233
|
+
if (doctrineRaw) {
|
|
234
|
+
try {
|
|
235
|
+
const parsed = JSON.parse(doctrineRaw);
|
|
236
|
+
if (Array.isArray(parsed.rules))
|
|
237
|
+
doctrines.push({
|
|
238
|
+
...(typeof parsed.version === "number"
|
|
239
|
+
? { version: parsed.version }
|
|
240
|
+
: {}),
|
|
241
|
+
...(typeof parsed.name === "string" ? { name: parsed.name } : {}),
|
|
242
|
+
rules: parsed.rules.filter((r) => typeof r === "string"),
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// Ilegível custa a doutrina, nunca a rodada.
|
|
247
|
+
}
|
|
248
|
+
}
|
|
229
249
|
}
|
|
230
250
|
return {
|
|
231
251
|
table: buildTable({ css, lock, source: adopted ? "adopted" : "installed" }),
|
|
232
252
|
recipes,
|
|
233
253
|
documents,
|
|
234
254
|
requires,
|
|
255
|
+
doctrines,
|
|
235
256
|
};
|
|
236
257
|
}
|
|
237
258
|
/**
|
package/dist/commands/mcp.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readdir, readFile } from "node:fs/promises";
|
|
|
3
3
|
import { join, relative, resolve } from "node:path";
|
|
4
4
|
import { pinnedHookVersion } from "../agent-wiring.js";
|
|
5
5
|
import { readToken, resolveRegistry } from "../config.js";
|
|
6
|
+
import { readEvents } from "../doctor/ledger.js";
|
|
6
7
|
import { fileRequest } from "../doctor/requests.js";
|
|
7
8
|
import { diagnose, scanSource } from "../doctor/scan.js";
|
|
8
9
|
import { nearestToken, tokenFor } from "../doctor/tokens.js";
|
|
@@ -70,6 +71,11 @@ const TOOLS = [
|
|
|
70
71
|
required: ["value"],
|
|
71
72
|
},
|
|
72
73
|
},
|
|
74
|
+
{
|
|
75
|
+
name: "system_doctrine",
|
|
76
|
+
description: "The rules and the philosophy of the installed system - maximum authority, read this BEFORE building anything. It answers from the version this project has pinned, which is the same version whose tokens are on disk, so the rules you follow and the values you write come from one place. It also says when a newer version has been published, without applying it.",
|
|
77
|
+
inputSchema: { type: "object", properties: {} },
|
|
78
|
+
},
|
|
73
79
|
{
|
|
74
80
|
name: "list_components",
|
|
75
81
|
description: "The components this design system defines, with what each is for. Look here BEFORE writing any UI element from scratch - if something covers the purpose, materialize it with add_component instead.",
|
|
@@ -252,6 +258,94 @@ async function findToken(root, value) {
|
|
|
252
258
|
// same refusal every time.
|
|
253
259
|
return `No token in this system holds ${value}, and nothing is close. Do NOT invent one. Say which value you need and what you would call it, and let a person decide.`;
|
|
254
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* A DOUTRINA DO SISTEMA - as regras e a filosofia, servidas em vez de materializadas.
|
|
263
|
+
*
|
|
264
|
+
* `rules.md` e `philosophy.md` eram escritos SÓ pelo `add` (e o `upgrade` chama o `add`), então entre
|
|
265
|
+
* um upgrade e outro eram uma cópia velha de dado que é VERSIONADO do nosso lado - e o GUIDE chamava
|
|
266
|
+
* um deles de "maximum authority". Uma autoridade máxima que envelhece em silêncio é pior que
|
|
267
|
+
* nenhuma (dono, 06/08).
|
|
268
|
+
*
|
|
269
|
+
* DA VERSÃO PINADA, e essa é a decisão que mantém as duas metades coerentes: os tokens no disco são
|
|
270
|
+
* os da v2, então a regra que o agente segue tem que ser a da v2. Servir o rascunho faria o agente
|
|
271
|
+
* escrever contra uma regra que ainda não está instalada.
|
|
272
|
+
*
|
|
273
|
+
* A existência de uma versão mais nova é DITA, nunca aplicada - aplicar é o `upgrade`, que é uma
|
|
274
|
+
* palavra que a pessoa já tem. Sem rede, a doutrina responde igual e a checagem é que se cala: o
|
|
275
|
+
* dado é local justamente para não depender de servidor nenhum.
|
|
276
|
+
*/
|
|
277
|
+
async function systemDoctrine(root) {
|
|
278
|
+
const { documents, doctrines } = await loadSystem(root);
|
|
279
|
+
const rules = doctrines.flatMap((d) => d.rules);
|
|
280
|
+
const parts = [];
|
|
281
|
+
const pinned = doctrines[0]?.version;
|
|
282
|
+
for (const doc of documents) {
|
|
283
|
+
const p = doc.philosophy;
|
|
284
|
+
if (p?.context)
|
|
285
|
+
parts.push(`## What this product is\n\n${p.context}`);
|
|
286
|
+
for (const sec of p?.sections ?? [])
|
|
287
|
+
parts.push(`## ${sec.title}\n\n${sec.body}`);
|
|
288
|
+
}
|
|
289
|
+
if (rules.length === 0 && parts.length === 0)
|
|
290
|
+
return "This system carries no rules and no philosophy yet. Nothing here overrides your judgement - build with its tokens and its components, and say what you needed and could not find.";
|
|
291
|
+
const head = [
|
|
292
|
+
rules.length > 0
|
|
293
|
+
? `# Rules - maximum authority, ${rules.length} of them${pinned ? `, as of v${pinned}` : ""}`
|
|
294
|
+
: "",
|
|
295
|
+
...rules.map((r) => `- ${r}`),
|
|
296
|
+
].filter(Boolean);
|
|
297
|
+
const pulse = await sessionPulse(root, pinned);
|
|
298
|
+
return [
|
|
299
|
+
...head,
|
|
300
|
+
...(parts.length > 0 ? ["", "# Philosophy", "", ...parts] : []),
|
|
301
|
+
...(pulse.length > 0 ? ["", ...pulse] : []),
|
|
302
|
+
].join("\n");
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* O PULSO DE UMA SESSÃO LONGA.
|
|
306
|
+
*
|
|
307
|
+
* A verificação de abertura (`align`) roda UMA vez, no minuto zero. Numa sessão de seis horas tudo o
|
|
308
|
+
* que ela achou é uma foto: a sessão expira na hora três, um colega publica na hora quatro, e o
|
|
309
|
+
* registro local cresce a tarde inteira sem subir. Nada disso volta a ser dito (dono, 06/08).
|
|
310
|
+
*
|
|
311
|
+
* Esta ferramenta é o lugar certo para o pulso porque o agente a chama ANTES de construir - que é
|
|
312
|
+
* exatamente o momento em que a consistência importa. Um `setInterval` avisaria no meio de outra
|
|
313
|
+
* coisa; isto avisa na fronteira de uma decisão.
|
|
314
|
+
*
|
|
315
|
+
* SÓ FALA QUANDO HÁ O QUE DIZER. Uma linha que aparece em toda chamada vira papel de parede numa
|
|
316
|
+
* ferramenta chamada dezenas de vezes por sessão, e aí ela não serve para a vez que importa.
|
|
317
|
+
*/
|
|
318
|
+
async function sessionPulse(root, pinned) {
|
|
319
|
+
const out = [];
|
|
320
|
+
const slug = await installedSlug(root);
|
|
321
|
+
const token = await readToken();
|
|
322
|
+
if (slug && token && pinned != null) {
|
|
323
|
+
const res = await fetch(`${resolveRegistry()}/api/registry/ds/${slug}`, {
|
|
324
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
325
|
+
}).catch(() => null);
|
|
326
|
+
/**
|
|
327
|
+
* 401 é a sessão que morreu no meio da sessão de trabalho, e é o pior silêncio possível: daqui
|
|
328
|
+
* em diante toda ferramenta falha e nenhuma diz por quê. Offline não produz linha - `res` nulo
|
|
329
|
+
* não distingue "sem rede" de "servidor fora", e nenhum dos dois é problema da pessoa.
|
|
330
|
+
*/
|
|
331
|
+
if (res?.status === 401)
|
|
332
|
+
out.push("> Your session on this machine has expired, so anything that needs the platform will fail from here on. A person has to run `npx synthesisui login` once in the terminal - I cannot complete it. What is on disk still governs: the rules above are the installed version's.");
|
|
333
|
+
else if (res?.ok) {
|
|
334
|
+
const body = (await res.json().catch(() => null));
|
|
335
|
+
if (body?.version && body.version > pinned)
|
|
336
|
+
out.push(`> v${body.version} of this system has been published and this project is on v${pinned}. These rules are v${pinned}'s, and so are the tokens on disk - the two move together. \`npx synthesisui upgrade ${slug}\` brings both.`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* O REGISTRO QUE NÃO SUBIU. Ele cresce a cada escrita verificada e só o `sync` o manda, então uma
|
|
341
|
+
* tarde inteira de trabalho fica invisível para a plataforma - e a tela do dono pontua o repo com
|
|
342
|
+
* evidência de ontem. Cinquenta é onde uma sessão de verdade chega; abaixo disso é ruído.
|
|
343
|
+
*/
|
|
344
|
+
const events = await readEvents(root).catch(() => []);
|
|
345
|
+
if (events.length >= 50)
|
|
346
|
+
out.push(`> ${events.length} checked writes are recorded here and have not been sent, so this repo is being scored on older evidence. \`npx synthesisui sync\` ships them.`);
|
|
347
|
+
return out;
|
|
348
|
+
}
|
|
255
349
|
async function listComponents(root) {
|
|
256
350
|
const { documents } = await loadSystem(root);
|
|
257
351
|
const rows = [];
|
|
@@ -839,6 +933,8 @@ cli) {
|
|
|
839
933
|
return text(await checkFile(root, String(args.path ?? ".")));
|
|
840
934
|
case "find_token":
|
|
841
935
|
return text(await findToken(root, String(args.value ?? "")));
|
|
936
|
+
case "system_doctrine":
|
|
937
|
+
return text(await systemDoctrine(root));
|
|
842
938
|
case "list_components":
|
|
843
939
|
return text(await listComponents(root));
|
|
844
940
|
case "describe_component":
|
package/dist/commands/use.js
CHANGED
|
@@ -43,15 +43,17 @@ export async function use(slug, intent, opts) {
|
|
|
43
43
|
const config = await readProjectConfig(root);
|
|
44
44
|
const { version, name } = lock;
|
|
45
45
|
const base = `_synthesisui/ds/${slug}`;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
/**
|
|
47
|
+
* A AUTORIDADE MÁXIMA VEM DE UMA FERRAMENTA, e o resto continua sendo caminho.
|
|
48
|
+
*
|
|
49
|
+
* `rules.md` e `philosophy.md` eram cópias velhas de dado versionado entre um upgrade e outro -
|
|
50
|
+
* ver `system_doctrine`. Um prompt que manda alguém abrir um arquivo que não existe mais é pior
|
|
51
|
+
* que um prompt sem a linha, então a condição passou a ser o DADO e não o arquivo.
|
|
52
|
+
*/
|
|
53
|
+
const hasDoctrine = await exists(join(slugDir, "doctrine.json"));
|
|
49
54
|
const readFirst = [
|
|
50
|
-
|
|
51
|
-
?
|
|
52
|
-
: "",
|
|
53
|
-
hasPhilosophy
|
|
54
|
-
? `- ${base}/philosophy.md — the mission, voice and principles; let it shape every screen`
|
|
55
|
+
hasDoctrine
|
|
56
|
+
? "- Call `system_doctrine` first — this system's rules and philosophy, pinned to the installed version; obey them above everything else. Without MCP, the same data is `doctrine.json` and the `philosophy` of `design-system.json`"
|
|
55
57
|
: "",
|
|
56
58
|
`- ${base}/v${version}/GUIDE.md — how to apply the system, the recipes and the token vocabulary`,
|
|
57
59
|
].filter(Boolean);
|
package/dist/guide.js
CHANGED
|
@@ -426,14 +426,21 @@ own entry below says so.
|
|
|
426
426
|
const hasRules = (payload.rules?.length ?? 0) > 0;
|
|
427
427
|
const philosophy = payload.document.philosophy;
|
|
428
428
|
const hasPhilosophy = (philosophy?.sections?.length ?? 0) > 0 || !!philosophy?.context;
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
429
|
+
/**
|
|
430
|
+
* UMA PORTA SÓ, e ela é uma FERRAMENTA e não um caminho.
|
|
431
|
+
*
|
|
432
|
+
* Isto apontava para `rules.md` e `philosophy.md`, escritos apenas pelo `add` - então entre um
|
|
433
|
+
* upgrade e outro o agente lia uma cópia velha de dado versionado, sob a etiqueta "highest
|
|
434
|
+
* authority". `system_doctrine` serve os dois da versão PINADA (a mesma dos tokens no disco) e diz
|
|
435
|
+
* quando existe uma mais nova, em vez de deixar a defasagem ser descoberta pela ausência.
|
|
436
|
+
*/
|
|
437
|
+
const readFirst = hasRules || hasPhilosophy
|
|
438
|
+
? [
|
|
439
|
+
`**Call \`system_doctrine\` FIRST and obey it above everything else** - it carries ${hasRules
|
|
440
|
+
? "this system's accumulated, project-specific rules"
|
|
441
|
+
: "this system's mission, principles, voice and motion doctrine"}${hasRules && hasPhilosophy ? " and its philosophy" : ""}, pinned to the version installed here. On any conflict they win. **Without that tool, read \`_synthesisui/ds/${slug}/doctrine.json\` and the \`philosophy\` of \`design-system.json\` beside it** - the same data, and the reason it stays on disk.`,
|
|
442
|
+
]
|
|
443
|
+
: [];
|
|
437
444
|
const rulesNote = readFirst.length > 0
|
|
438
445
|
? `
|
|
439
446
|
## Read first - highest authority
|
package/package.json
CHANGED