synthesisui 0.16.451 → 0.16.456
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/agent-wiring.js +88 -88
- package/dist/auto-sync.js +88 -0
- package/dist/claude-md.js +3 -2
- package/dist/commands/add.js +12 -0
- package/dist/commands/align.js +2 -2
- package/dist/commands/connect.js +67 -12
- package/dist/commands/hook.js +84 -8
- package/dist/commands/upgrade.js +1 -1
- package/dist/copy/connect.pt-BR.js +11 -6
- package/dist/guarantee.js +2 -2
- package/dist/install-marks.js +33 -2
- package/dist/report-insight.js +3 -3
- package/package.json +1 -1
package/dist/agent-wiring.js
CHANGED
|
@@ -4,30 +4,20 @@ import { dirname, join } from "node:path";
|
|
|
4
4
|
import { hasCodexBlock, pinnedInCodex, withCodexBlock } from "./codex-mcp.js";
|
|
5
5
|
import { wrapperSource } from "./statusline-wrapper.js";
|
|
6
6
|
/**
|
|
7
|
-
* QUANDO A CHECAGEM RODA -
|
|
7
|
+
* QUANDO A CHECAGEM RODA - no FIM DO TURNO do agente, desde 23/09.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* agente
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* frontend - não quando o agente segura a caneta certa.
|
|
16
|
-
*
|
|
17
|
-
* Um comando de shell não diz quais arquivos tocou, então quem responde é a árvore de trabalho -
|
|
18
|
-
* ver `changed-files.ts`, onde também está o custo medido.
|
|
9
|
+
* Ela rodava depois de cada escrita (`PostToolUse`, com o filtro `Write|Edit|MultiEdit|Bash` desde
|
|
10
|
+
* 12/09, quando se mediu que toda edição em lote do `codelevel` passava por shell). A escolha dele
|
|
11
|
+
* em 23/09, olhando o ledger do mesmo repositório - 850 execuções, 3 checagens em 303 com achado:
|
|
12
|
+
* *"no fim de cada turno do agente, sobre o diff"*. O `Stop` não tem filtro: ele roda uma vez por
|
|
13
|
+
* turno, e quem acha o que o turno escreveu é a árvore de trabalho (`changed-files.ts`), seja qual
|
|
14
|
+
* for a ferramenta que o agente usou.
|
|
19
15
|
*/
|
|
20
|
-
const
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
* Quem conectou antes disso tem no arquivo dele um filtro que NÓS escrevemos, e deixá-lo como está
|
|
26
|
-
* seria entregar a correção só para quem instala do zero. Alargar um filtro que uma PESSOA
|
|
27
|
-
* escreveu é outra coisa, e continua proibido: a comparação é por igualdade exata com a nossa
|
|
28
|
-
* string antiga, nunca por parecer com ela.
|
|
29
|
-
*/
|
|
30
|
-
const HOOK_MATCHER_BEFORE_SHELL = "Write|Edit|MultiEdit";
|
|
16
|
+
const HOOK_EVENT = "Stop";
|
|
17
|
+
/** O evento de ANTES - onde uma instalação anterior a 23/09 tem o nosso hook, e de onde ele sai. */
|
|
18
|
+
const HOOK_EVENT_BEFORE = "PostToolUse";
|
|
19
|
+
/** O NOSSO hook de checagem, e só ele: o `align` da abertura de sessão também diz "synthesisui". */
|
|
20
|
+
const isOurHook = (command) => /\bsynthesisui(@\S+)? hook\b/.test(command ?? "");
|
|
31
21
|
/**
|
|
32
22
|
* Exportado porque a REGRA é uma só: a pasta de uma ferramenta é a evidência de que ela é usada, e
|
|
33
23
|
* a mesma evidência decide o MCP daqui e a casa do bloco em `claude-md.ts`. Duas cópias deste
|
|
@@ -111,87 +101,98 @@ export async function alignCommand(root, version) {
|
|
|
111
101
|
export async function pinnedHookVersion(root) {
|
|
112
102
|
const settings = await readJson(join(root, ".claude", "settings.json")).catch(() => ({}));
|
|
113
103
|
const hooks = (settings.hooks ?? {});
|
|
114
|
-
|
|
115
|
-
for (const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
104
|
+
/** O `Stop` primeiro: um projeto no meio da mudança lê a versão de onde o hook roda agora. */
|
|
105
|
+
for (const event of [HOOK_EVENT, HOOK_EVENT_BEFORE]) {
|
|
106
|
+
const entries = Array.isArray(hooks[event]) ? hooks[event] : [];
|
|
107
|
+
for (const entry of entries)
|
|
108
|
+
for (const h of entry.hooks ?? []) {
|
|
109
|
+
const command = h.command ?? "";
|
|
110
|
+
if (!isOurHook(command))
|
|
111
|
+
continue;
|
|
112
|
+
if (/--no-install synthesisui hook/.test(command))
|
|
113
|
+
return null;
|
|
114
|
+
const m = /synthesisui@(\d+\.\d+\.\d+)/.exec(command);
|
|
115
|
+
if (m)
|
|
116
|
+
return m[1];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
124
119
|
return null;
|
|
125
120
|
}
|
|
126
121
|
async function wireHook(root, command) {
|
|
127
122
|
const dir = join(root, ".claude");
|
|
128
123
|
const path = join(dir, "settings.json");
|
|
129
124
|
const settings = await readJson(path);
|
|
130
|
-
const hooks = (settings.hooks ?? {});
|
|
131
|
-
const post = Array.isArray(hooks.PostToolUse) ? hooks.PostToolUse : [];
|
|
125
|
+
const hooks = { ...(settings.hooks ?? {}) };
|
|
132
126
|
/**
|
|
133
|
-
* O
|
|
127
|
+
* O HOOK DE ANTES SAI, e só ele - E02 da jornada "a checagem no fim do turno".
|
|
134
128
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* - "rode connect, depois reinicie" - mandava rodar um comando que não fazia a coisa.
|
|
140
|
-
*
|
|
141
|
-
* Agora o comando é REESCRITO no lugar quando ele é nosso e difere. O que a idempotência original
|
|
142
|
-
* protegia continua protegido: o matcher fica como estiver, os hooks vizinhos ficam, e uma segunda
|
|
143
|
-
* entrada nunca é criada - duas entradas rodariam o verificador duas vezes por escrita.
|
|
129
|
+
* Um projeto conectado antes de 23/09 tem o nosso hook no `PostToolUse`. Deixá-lo lá rodaria a
|
|
130
|
+
* checagem duas vezes - por escrita e no fim do turno -, que é o ruído que a mudança existe para
|
|
131
|
+
* tirar. Sai o NOSSO comando, e nada além: um hook dele na mesma entrada fica, com o filtro que
|
|
132
|
+
* ele escreveu, e a entrada só some quando fica vazia.
|
|
144
133
|
*/
|
|
145
|
-
|
|
146
|
-
const
|
|
134
|
+
let was;
|
|
135
|
+
const before = Array.isArray(hooks[HOOK_EVENT_BEFORE])
|
|
136
|
+
? hooks[HOOK_EVENT_BEFORE]
|
|
137
|
+
: [];
|
|
138
|
+
const kept = [];
|
|
139
|
+
for (const entry of before) {
|
|
140
|
+
const mine = (entry.hooks ?? []).filter((h) => isOurHook(h.command));
|
|
141
|
+
if (mine.length === 0) {
|
|
142
|
+
kept.push(entry);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
was = was ?? mine[0]?.command;
|
|
146
|
+
const theirs = (entry.hooks ?? []).filter((h) => !isOurHook(h.command));
|
|
147
|
+
if (theirs.length > 0)
|
|
148
|
+
kept.push({ ...entry, hooks: theirs });
|
|
149
|
+
}
|
|
150
|
+
if (was !== undefined) {
|
|
151
|
+
if (kept.length > 0)
|
|
152
|
+
hooks[HOOK_EVENT_BEFORE] = kept;
|
|
153
|
+
else
|
|
154
|
+
delete hooks[HOOK_EVENT_BEFORE];
|
|
155
|
+
}
|
|
156
|
+
const stop = Array.isArray(hooks[HOOK_EVENT]) ? [...hooks[HOOK_EVENT]] : [];
|
|
157
|
+
const owner = stop.find((e) => (e.hooks ?? []).some((h) => isOurHook(h.command)));
|
|
158
|
+
const mine = (owner?.hooks ?? []).find((h) => isOurHook(h.command));
|
|
159
|
+
const save = async () => {
|
|
160
|
+
await mkdir(dir, { recursive: true });
|
|
161
|
+
await writeFile(path, `${JSON.stringify({ ...settings, hooks: { ...hooks, [HOOK_EVENT]: stop } }, null, 2)}\n`, "utf8");
|
|
162
|
+
};
|
|
147
163
|
if (owner && mine) {
|
|
148
|
-
const
|
|
149
|
-
/**
|
|
150
|
-
* O FILTRO NOSSO ANTIGO SOBE JUNTO - ver `HOOK_MATCHER_BEFORE_SHELL`.
|
|
151
|
-
*
|
|
152
|
-
* Sem isto, a checagem passar a valer para edição por shell alcançaria só quem instalasse do
|
|
153
|
-
* zero: quem já tinha conectado ficaria com o filtro estreito para sempre, e nada no produto
|
|
154
|
-
* diria isso a ele.
|
|
155
|
-
*
|
|
156
|
-
* **E SÓ QUANDO A ENTRADA É NOSSA INTEIRA** - achado da revisão de contrato no fecho, e a
|
|
157
|
-
* fronteira estava larga demais. `matcher` vale para TODOS os comandos da entrada: se ele
|
|
158
|
-
* acrescentou um hook dele ali ao lado - um formatador, um linter -, alargar o filtro passaria
|
|
159
|
-
* a disparar o comando DELE depois de todo comando de shell, sem ele ter pedido. É a mesma lei
|
|
160
|
-
* que impede escrever arquivo de um agente que ele não escolheu.
|
|
161
|
-
*/
|
|
162
|
-
const onlyOurs = (owner.hooks ?? []).every((h) => (h.command ?? "").includes("synthesisui"));
|
|
163
|
-
const widen = owner.matcher === HOOK_MATCHER_BEFORE_SHELL && onlyOurs;
|
|
164
|
-
if (widen)
|
|
165
|
-
owner.matcher = HOOK_MATCHER;
|
|
164
|
+
const there = mine.command ?? "";
|
|
166
165
|
/**
|
|
167
166
|
* SÓ UM PIN NOSSO É SUBSTITUÍDO, e a fronteira é estreita de propósito.
|
|
168
167
|
*
|
|
169
168
|
* Um comando SEM versão (`npx synthesisui hook`) ou com instalação local é escolha deliberada de
|
|
170
|
-
* alguém: reescrevê-lo seria pinar a decisão da pessoa
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
* `npx synthesisui@0.16.153 hook` como "already had it").
|
|
169
|
+
* alguém: reescrevê-lo seria pinar a decisão da pessoa. O que sobe é `synthesisui@<semver>` para
|
|
170
|
+
* outro semver, que é o único caso em que "already there" mentia (dono, 06/08: `connect` do
|
|
171
|
+
* 0.16.157 imprimindo `npx synthesisui@0.16.153 hook` como "already had it").
|
|
174
172
|
*/
|
|
175
|
-
const pinnedAt = /synthesisui@(\d+\.\d+\.\d+)/.exec(
|
|
173
|
+
const pinnedAt = /synthesisui@(\d+\.\d+\.\d+)/.exec(there)?.[1];
|
|
176
174
|
const proposedAt = /synthesisui@(\d+\.\d+\.\d+)/.exec(command)?.[1];
|
|
177
175
|
const samePin = !pinnedAt || !proposedAt || pinnedAt === proposedAt;
|
|
178
|
-
if (samePin && !widen)
|
|
179
|
-
return { status: "already there", command: was || command };
|
|
180
176
|
if (!samePin)
|
|
181
177
|
mine.command = command;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
178
|
+
if (samePin && was === undefined)
|
|
179
|
+
return { status: "already there", command: there || command };
|
|
180
|
+
await save();
|
|
181
|
+
if (was !== undefined)
|
|
182
|
+
return { status: "moved", command: mine.command ?? command, was };
|
|
183
|
+
return { status: "updated", command, was: there };
|
|
187
184
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
185
|
+
/**
|
|
186
|
+
* O COMANDO QUE VAI PARA O FIM DO TURNO. Quem vem do hook de antes leva a escolha que fez lá - um
|
|
187
|
+
* comando sem versão, ou a instalação local, continua sendo dele. Um pin nosso sobe para esta
|
|
188
|
+
* versão: o hook pinado antes de 0.16.452 não conhece o `Stop`, e ali ele ficaria mudo.
|
|
189
|
+
*/
|
|
190
|
+
const carried = was !== undefined && !/synthesisui@\d+\.\d+\.\d+/.test(was) ? was : command;
|
|
191
|
+
stop.push({ hooks: [{ type: "command", command: carried }] });
|
|
192
|
+
await save();
|
|
193
|
+
return was !== undefined
|
|
194
|
+
? { status: "moved", command: carried, was }
|
|
195
|
+
: { status: "added", command };
|
|
195
196
|
}
|
|
196
197
|
/**
|
|
197
198
|
* O MESMO SERVIDOR MCP, NOS CAMINHOS QUE CADA AGENTE LÊ.
|
|
@@ -483,9 +484,8 @@ async function wireSessionStart(root, command) {
|
|
|
483
484
|
*/
|
|
484
485
|
export async function hasHook(root) {
|
|
485
486
|
const settings = await readJson(join(root, ".claude", "settings.json")).catch(() => ({}));
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
return post.some((e) => (e.hooks ?? []).some((h) => (h.command ?? "").includes("synthesisui")));
|
|
487
|
+
const hooks = (settings.hooks ?? {});
|
|
488
|
+
/** Os dois eventos: um projeto que ainda não rodou o `connect` novo continua com a garantia. */
|
|
489
|
+
return [HOOK_EVENT, HOOK_EVENT_BEFORE].some((event) => Array.isArray(hooks[event]) &&
|
|
490
|
+
hooks[event].some((e) => (e.hooks ?? []).some((h) => isOurHook(h.command))));
|
|
491
491
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* O SYNC NO FIM DO TURNO, COM O SIM DE QUEM INSTALA - E03 da jornada "a checagem no fim do turno".
|
|
6
|
+
*
|
|
7
|
+
* A escolha dele em 23/09: *"no fim de cada turno do agente, sobre o diff, fazendo o sync no mesmo
|
|
8
|
+
* passo"*. A pergunta que a abriu: *"isso agora vai ser automático quando rodar o Synth lá?"*. No
|
|
9
|
+
* CodeLevel, entre 22/09 e 23/09, 984 linhas do ledger ficaram paradas porque ninguém rodou `sync`,
|
|
10
|
+
* e o painel dizia que o agente tinha trabalhado menos do que trabalhou.
|
|
11
|
+
*
|
|
12
|
+
* ═══ A RESPOSTA É DESTA MÁQUINA, e não do repositório ═══
|
|
13
|
+
*
|
|
14
|
+
* `_synthesisui/config.json` é versionado: um sim gravado lá faria o colega que clona o repositório
|
|
15
|
+
* enviar sem ter sido perguntado. É o mesmo motivo do `.mode` (`mode.ts`): a escolha de uma pessoa
|
|
16
|
+
* nunca decide por outra. O arquivo mora ao lado, na lista do que o `.gitignore` da pasta cobre.
|
|
17
|
+
*
|
|
18
|
+
* ═══ NUNCA PERGUNTADO É NÃO ═══
|
|
19
|
+
*
|
|
20
|
+
* Sem o arquivo, nada é enviado - a promessa que o produto fazia até aqui era "nada sai da máquina
|
|
21
|
+
* sem você rodar um comando", e ela continua valendo para quem não disse sim.
|
|
22
|
+
*/
|
|
23
|
+
export const autoSyncPath = (root) => join(root, "_synthesisui", ".auto-sync");
|
|
24
|
+
export async function readAutoSync(root) {
|
|
25
|
+
const raw = await readFile(autoSyncPath(root), "utf8").catch(() => null);
|
|
26
|
+
if (raw == null)
|
|
27
|
+
return null;
|
|
28
|
+
const value = raw.trim().toLowerCase();
|
|
29
|
+
return value === "yes" ? true : value === "no" ? false : null;
|
|
30
|
+
}
|
|
31
|
+
export async function writeAutoSync(root, yes) {
|
|
32
|
+
await writeFile(autoSyncPath(root), yes ? "yes\n" : "no\n", "utf8");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* QUANTO TEMPO UM SYNC DISPARADO SEGURA O PRÓXIMO.
|
|
36
|
+
*
|
|
37
|
+
* Um agente encerra turnos em sequência, às vezes a cada poucos segundos, e cada `sync` relê o
|
|
38
|
+
* repositório inteiro e fala com o servidor. Dois ao mesmo tempo escreveriam o mesmo cursor
|
|
39
|
+
* (`.ledger-sent`) e poderiam mandar as mesmas linhas duas vezes. Dois minutos cobrem uma medição
|
|
40
|
+
* com folga; o que ficar para trás vai no sync seguinte, porque o ledger só anda para frente.
|
|
41
|
+
*
|
|
42
|
+
* A LACUNA DECLARADA: o último turno de uma sessão, se cair dentro da janela, só sobe no próximo
|
|
43
|
+
* sync - numa sessão seguinte, ou quando alguém rodar o comando.
|
|
44
|
+
*/
|
|
45
|
+
export const SYNC_GAP_MS = 2 * 60_000;
|
|
46
|
+
const lockPath = (root) => join(root, "_synthesisui", ".sync-started");
|
|
47
|
+
/**
|
|
48
|
+
* Dispara o `sync` DESTACADO e volta na hora - o turno do agente não espera a rede.
|
|
49
|
+
*
|
|
50
|
+
* O binário é o MESMO que está rodando o hook (`process.argv[1]`): o `npx synthesisui@<v> hook`
|
|
51
|
+
* pinado resolve para um caminho, e o sync sai daquele caminho, na mesma versão. Um `npx` novo
|
|
52
|
+
* resolveria outra vez no registro, que é a espera que a instalação local existe para evitar.
|
|
53
|
+
*
|
|
54
|
+
* `stdio: "ignore"` também é a razão de ele nunca travar: sem terminal, toda pergunta do `sync`
|
|
55
|
+
* (`askToOverwrite`, `askForScope`) responde não sozinha.
|
|
56
|
+
*/
|
|
57
|
+
export async function startSync(root) {
|
|
58
|
+
const last = await stat(lockPath(root)).then((s) => s.mtimeMs, () => 0);
|
|
59
|
+
if (Date.now() - last < SYNC_GAP_MS)
|
|
60
|
+
return false;
|
|
61
|
+
await writeFile(lockPath(root), "", "utf8");
|
|
62
|
+
const entry = process.argv[1];
|
|
63
|
+
if (!entry) {
|
|
64
|
+
await rm(lockPath(root), { force: true });
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
/**
|
|
69
|
+
* SÓ AS CHECAGENS - jornada "o sistema só muda com commit", E01 (dono, 27/09: *"no fim do turno
|
|
70
|
+
* sobem só as checagens (...) nada publica de trabalho em andamento"*). O `sync` inteiro remede o
|
|
71
|
+
* repositório e manda o censo, e a rota do censo chama o Autopilot: um turno pela metade, numa
|
|
72
|
+
* branch de trabalho, podia virar versão publicada (medido no CodeLevel, 27/09 00:49).
|
|
73
|
+
* `--record-only` manda o registro e o estado do repositório, e não remede.
|
|
74
|
+
*/
|
|
75
|
+
const child = spawn(process.execPath, [entry, "sync", "--record-only"], {
|
|
76
|
+
cwd: root,
|
|
77
|
+
detached: true,
|
|
78
|
+
stdio: "ignore",
|
|
79
|
+
env: process.env,
|
|
80
|
+
});
|
|
81
|
+
child.on?.("error", () => { });
|
|
82
|
+
child.unref();
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
package/dist/claude-md.js
CHANGED
|
@@ -262,8 +262,9 @@ Better than remembering: \`npx synthesisui@latest connect\` installs the check a
|
|
|
262
262
|
an editor hook, so it runs on its own and this paragraph stops being your job.${VERIFY}`;
|
|
263
263
|
const SELF_CHECK_HOOKED = `
|
|
264
264
|
|
|
265
|
-
**The check runs by itself.**
|
|
266
|
-
token this project already has for anything
|
|
265
|
+
**The check runs by itself.** At the end of every turn, a hook reports on the
|
|
266
|
+
files the turn changed, naming the token this project already has for anything
|
|
267
|
+
hardcoded. You do not need to run
|
|
267
268
|
\`doctor\` by hand, and you should not wait for a final pass.
|
|
268
269
|
|
|
269
270
|
When it names something, fix it in that file before moving to the next one -
|
package/dist/commands/add.js
CHANGED
|
@@ -59,6 +59,18 @@ const IGNORED = [
|
|
|
59
59
|
* outra"*. Esta lista reconcilia, então quem já está conectado também recebe a linha.
|
|
60
60
|
*/
|
|
61
61
|
".mode",
|
|
62
|
+
/**
|
|
63
|
+
* O SIM DELA PARA O ENVIO NO FIM DO TURNO - ver `auto-sync.ts`. Pelo mesmo motivo do `.mode`:
|
|
64
|
+
* commitado, o sim de uma pessoa enviaria da máquina de todo o time.
|
|
65
|
+
*/
|
|
66
|
+
".auto-sync",
|
|
67
|
+
/** A trava que impede dois syncs ao mesmo tempo - descreve ESTE clone. */
|
|
68
|
+
".sync-started",
|
|
69
|
+
/**
|
|
70
|
+
* O RELÓGIO DO FIM DO TURNO (`hook.ts`, E01 de 23/09). Ele nasceu fora desta lista, e o primeiro
|
|
71
|
+
* turno de quem instalasse faria um arquivo nosso aparecer no `git status` dele.
|
|
72
|
+
*/
|
|
73
|
+
".turn-clock",
|
|
62
74
|
];
|
|
63
75
|
const IGNORE_HEADER = "# Managed by synthesisui. The identity and the CSS are committed so a fresh\n" +
|
|
64
76
|
"# clone is governed; the measurement and the local record are not, because\n" +
|
package/dist/commands/align.js
CHANGED
|
@@ -333,7 +333,7 @@ opts = {}) {
|
|
|
333
333
|
*/
|
|
334
334
|
if (folderBehind && hookBehind && folderBehind === hookBehind)
|
|
335
335
|
out.push({
|
|
336
|
-
says: `you are running CLI ${cli} and two things here are still on ${folderBehind}: the files under _synthesisui/ds/${lock.slug}, and the check that runs
|
|
336
|
+
says: `you are running CLI ${cli} and two things here are still on ${folderBehind}: the files under _synthesisui/ds/${lock.slug}, and the check that runs at the end of each agent turn - so your agent's work is read by the older one.`,
|
|
337
337
|
run: "npx synthesisui upgrade",
|
|
338
338
|
});
|
|
339
339
|
else {
|
|
@@ -344,7 +344,7 @@ opts = {}) {
|
|
|
344
344
|
});
|
|
345
345
|
if (hookBehind)
|
|
346
346
|
out.push({
|
|
347
|
-
says: `the check that runs
|
|
347
|
+
says: `the check that runs at the end of each agent turn is pinned to CLI ${hookBehind} and you are running ${cli} - your agent's work is being checked by an older reader than the one measuring this repo.`,
|
|
348
348
|
run: "npx synthesisui upgrade",
|
|
349
349
|
});
|
|
350
350
|
}
|
package/dist/commands/connect.js
CHANGED
|
@@ -2,6 +2,7 @@ import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { codexPinBefore, exists, wireAgent } from "../agent-wiring.js";
|
|
4
4
|
import { AGENTS, chooseAgents, filesOf, rememberChoice, } from "../agents-chosen.js";
|
|
5
|
+
import { readAutoSync, writeAutoSync } from "../auto-sync.js";
|
|
5
6
|
import { blockHomes, syncClaudeMd } from "../claude-md.js";
|
|
6
7
|
import { resolveRegistry } from "../config.js";
|
|
7
8
|
import { guaranteeIfNew } from "../guarantee.js";
|
|
@@ -25,7 +26,7 @@ import { installedSlugs } from "./sync.js";
|
|
|
25
26
|
* of them and got, instead, the CLAUDE.md instruction we had already measured
|
|
26
27
|
* being ignored.
|
|
27
28
|
*
|
|
28
|
-
* hook guarantees runs
|
|
29
|
+
* hook guarantees runs at the end of every agent turn, whether the agent likes it or not
|
|
29
30
|
* mcp audits lets the agent ask the system instead of guessing
|
|
30
31
|
* md informs and now stops repeating what the hook already ensures
|
|
31
32
|
*
|
|
@@ -451,18 +452,50 @@ export async function connect(opts) {
|
|
|
451
452
|
* de duas frases que a etapa 11 fechou no `template`, aparecendo na tela que a 12 acabou de
|
|
452
453
|
* escrever - e `wired.hook` diz `skipped`, então a informação para suprimir já estava aqui.
|
|
453
454
|
*/
|
|
455
|
+
/**
|
|
456
|
+
* O CODEX NÃO TEM FIM DE TURNO QUE SE SEGURE - E02 da jornada "a checagem no fim do turno". A
|
|
457
|
+
* checagem mora no `Stop` do Claude Code; num repositório que escolheu só o Codex ela não existe,
|
|
458
|
+
* e a tela diz isso em vez de deixar ele supor que o agente dele está coberto.
|
|
459
|
+
*/
|
|
460
|
+
if (want.hook && wired.hook === "skipped" && chosen.agents.includes("codex"))
|
|
461
|
+
/** `true`: é aviso, não estado - uma linha "já estava lá" some no resumo, e esta não pode. */
|
|
462
|
+
row(true, say("· Codex has no end-of-turn hook we can hold - run `npx synthesisui doctor` to check what it wrote"));
|
|
463
|
+
/**
|
|
464
|
+
* O ENVIO NO FIM DO TURNO, PERGUNTADO UMA VEZ - E03 da jornada "a checagem no fim do turno".
|
|
465
|
+
*
|
|
466
|
+
* A pergunta existe porque a promessa mudou: até aqui, nada saía da máquina dele sem ele rodar um
|
|
467
|
+
* comando. Quem diz sim passa a ter o painel em dia sozinho; quem diz não - ou quem nunca foi
|
|
468
|
+
* perguntado, como um CI - continua exatamente como antes. A resposta mora em `.auto-sync`, fora do
|
|
469
|
+
* git (ver `auto-sync.ts`), e com ela gravada a pergunta não volta.
|
|
470
|
+
*/
|
|
471
|
+
if (want.hook && wired.hook !== "skipped") {
|
|
472
|
+
const before = await readAutoSync(root).catch(() => null);
|
|
473
|
+
const now = before ?? (await askToSendAtTurnEnd());
|
|
474
|
+
if (before === null && now !== null)
|
|
475
|
+
await writeAutoSync(root, now);
|
|
476
|
+
row(before === null, now === true
|
|
477
|
+
? say("✓ what the check records is sent to your dashboard at the end of each turn")
|
|
478
|
+
: say("· what the check records stays on this machine - run `npx synthesisui sync` to send it"));
|
|
479
|
+
}
|
|
454
480
|
if (want.hook && wired.hook !== "skipped") {
|
|
455
481
|
row(wired.hook !== "already there", (() => {
|
|
456
482
|
return wired.hook === "added"
|
|
457
|
-
? say("✓ .claude/settings.json the check now runs
|
|
458
|
-
: wired.hook === "
|
|
483
|
+
? say("✓ .claude/settings.json the check now runs at the end of each agent turn")
|
|
484
|
+
: wired.hook === "moved"
|
|
459
485
|
? /**
|
|
460
|
-
* A
|
|
461
|
-
*
|
|
462
|
-
*
|
|
486
|
+
* A CHECAGEM MUDOU DE LUGAR, dita como mudança - E02 da jornada "a checagem no fim do
|
|
487
|
+
* turno". Quem tinha o hook por escrita precisa saber que ele não roda mais ali, ou vai
|
|
488
|
+
* estranhar o silêncio no meio do turno.
|
|
463
489
|
*/
|
|
464
|
-
|
|
465
|
-
:
|
|
490
|
+
say("✓ .claude/settings.json the check moved from every write to the end of each agent turn")
|
|
491
|
+
: wired.hook === "updated"
|
|
492
|
+
? /**
|
|
493
|
+
* A LINHA QUE FALTAVA, e a ausência dela fazia o comando mentir: rodando o 0.16.157, a
|
|
494
|
+
* saída dizia "already had it" e imprimia `npx synthesisui@0.16.153 hook` embaixo
|
|
495
|
+
* (dono, 06/08). Quem lê "already had it" fecha o terminal.
|
|
496
|
+
*/
|
|
497
|
+
`✓ .claude/settings.json the check moved from ${wired.was?.match(/synthesisui@(\d+\.\d+\.\d+)/)?.[1] ?? "an older version"} to this one`
|
|
498
|
+
: say("· .claude/settings.json already had it");
|
|
466
499
|
})(), wired.command);
|
|
467
500
|
/**
|
|
468
501
|
* A SEGUNDA COSTURA, dita por nome. As onze ferramentas MCP são PULL e o hook de escrita roda
|
|
@@ -677,7 +710,7 @@ export async function connect(opts) {
|
|
|
677
710
|
* interrompido precisa saber que só apagar a região para isso.
|
|
678
711
|
*/
|
|
679
712
|
if (entries.includes(".claude/settings.json"))
|
|
680
|
-
console.log(body(paint.faint(say(" the
|
|
713
|
+
console.log(body(paint.faint(say(" the turn check inside it keeps running until that entry is gone"))));
|
|
681
714
|
}
|
|
682
715
|
/**
|
|
683
716
|
* O CURSOR SAIU DO PRODUTO (A6 da etapa 12), e quem tem os arquivos dele é avisado com os
|
|
@@ -760,7 +793,7 @@ export async function connect(opts) {
|
|
|
760
793
|
* Um pedido que aparece sempre é um pedido que a pessoa passa a ignorar - e aí ele não serve para
|
|
761
794
|
* a vez em que era mesmo obrigatório.
|
|
762
795
|
*/
|
|
763
|
-
const moved = (s) => s === "added" || s === "updated";
|
|
796
|
+
const moved = (s) => s === "added" || s === "updated" || s === "moved";
|
|
764
797
|
const needsRestart = moved(wired.hook) ||
|
|
765
798
|
moved(wired.session) ||
|
|
766
799
|
(Array.isArray(wired.mcp) && wired.mcp.some((m) => moved(m.status)));
|
|
@@ -778,7 +811,7 @@ export async function connect(opts) {
|
|
|
778
811
|
...(bothChecks
|
|
779
812
|
? [say("the checks")]
|
|
780
813
|
: moved(wired.hook)
|
|
781
|
-
? [say("the
|
|
814
|
+
? [say("the turn check")]
|
|
782
815
|
: moved(wired.session)
|
|
783
816
|
? [say("the session check")]
|
|
784
817
|
: []),
|
|
@@ -825,8 +858,30 @@ export async function connect(opts) {
|
|
|
825
858
|
* `apps/web/src/lib/cli/commands.spec.ts`.
|
|
826
859
|
*/
|
|
827
860
|
console.log("");
|
|
828
|
-
console.log(bodyWrapped(say("The hook resolves against the npm registry
|
|
861
|
+
console.log(bodyWrapped(say("The hook resolves against the npm registry at the end of each turn - that is the wait, not the check. Add synthesisui to your devDependencies and run this again; it switches by itself to local, several times faster."))
|
|
829
862
|
.map(paint.dim)
|
|
830
863
|
.join("\n"));
|
|
831
864
|
}
|
|
832
865
|
}
|
|
866
|
+
/**
|
|
867
|
+
* Pergunta uma vez, e só num terminal de verdade - o mesmo molde do `askToOverwrite` do `sync`.
|
|
868
|
+
*
|
|
869
|
+
* Sem TTY devolve `null`: não há quem responder, e nada é gravado. O padrão do terminal é NÃO
|
|
870
|
+
* (`[y/N]`), porque enviar é a mudança, e uma mudança de promessa pede um sim dito.
|
|
871
|
+
*/
|
|
872
|
+
async function askToSendAtTurnEnd() {
|
|
873
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
874
|
+
return null;
|
|
875
|
+
const { createInterface } = await import("node:readline/promises");
|
|
876
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
877
|
+
try {
|
|
878
|
+
const answer = await rl.question(` ${say("send what the check records to your dashboard at the end of each turn?")} [y/N]: `);
|
|
879
|
+
return /^(y(es)?|s(im)?)$/i.test(answer.trim());
|
|
880
|
+
}
|
|
881
|
+
catch {
|
|
882
|
+
return null;
|
|
883
|
+
}
|
|
884
|
+
finally {
|
|
885
|
+
rl.close();
|
|
886
|
+
}
|
|
887
|
+
}
|
package/dist/commands/hook.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join, relative, resolve } from "node:path";
|
|
3
|
+
import { readAutoSync, startSync } from "../auto-sync.js";
|
|
3
4
|
import { changedSince } from "../changed-files.js";
|
|
4
5
|
import { emptyTally, exportedNames, internalSpecifiers, scanComponentsInto, tallyToInventory, } from "../doctor/components-scan.js";
|
|
5
6
|
import { checkContracts } from "../doctor/contract-check.js";
|
|
@@ -77,6 +78,16 @@ const clockOf = async (root) => stat(ledgerPath(root)).then((s) => s.mtimeMs, ()
|
|
|
77
78
|
* que o produto acabou de ser instalado - e a tela do `connect` já disse que ele está vivo.
|
|
78
79
|
*/
|
|
79
80
|
const startClock = (root) => writeFile(ledgerPath(root), "", { flag: "a" }).catch(() => { });
|
|
81
|
+
/**
|
|
82
|
+
* O RELÓGIO DO TURNO É PRÓPRIO, e não o do ledger - E01 da jornada "a checagem no fim do turno".
|
|
83
|
+
*
|
|
84
|
+
* O ledger anda a cada comando que escreve nele: um `doctor`, um `align` ou o próprio MCP no MEIO
|
|
85
|
+
* do turno moveriam o relógio, e o fim do turno deixaria de ver tudo o que foi escrito antes
|
|
86
|
+
* deles. Um arquivo só deste relógio anda uma vez por turno, no fim, depois de olhar.
|
|
87
|
+
*/
|
|
88
|
+
const TURN_CLOCK = "_synthesisui/.turn-clock";
|
|
89
|
+
const turnClockOf = (root) => stat(join(root, TURN_CLOCK)).then((s) => s.mtimeMs, () => 0);
|
|
90
|
+
const moveTurnClock = (root) => writeFile(join(root, TURN_CLOCK), "", "utf8").catch(() => { });
|
|
80
91
|
/**
|
|
81
92
|
* QUANTOS ARQUIVOS UM COMANDO DE SHELL PODE FAZER O PRODUTO RELATAR DE UMA VEZ.
|
|
82
93
|
*
|
|
@@ -123,7 +134,7 @@ async function greet(root, rel) {
|
|
|
123
134
|
return [
|
|
124
135
|
`${rel} - checked, nothing to name. Every value in it has a token.`,
|
|
125
136
|
"",
|
|
126
|
-
"That check now runs
|
|
137
|
+
"That check now runs at the end of every turn, by itself. Tell the person once",
|
|
127
138
|
"that it is live, so they know it installed, and then stop mentioning it - from",
|
|
128
139
|
"here it only speaks when something needs a name.",
|
|
129
140
|
].join("\n");
|
|
@@ -425,6 +436,10 @@ export async function hook(opts) {
|
|
|
425
436
|
process.stdout.write(`${JSON.stringify(pass())}\n`);
|
|
426
437
|
return;
|
|
427
438
|
}
|
|
439
|
+
if (input.hook_event_name === "Stop") {
|
|
440
|
+
await endOfTurn(root, input);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
428
443
|
if (input.hook_event_name !== "PostToolUse") {
|
|
429
444
|
process.stdout.write(`${JSON.stringify(pass())}\n`);
|
|
430
445
|
return;
|
|
@@ -464,6 +479,23 @@ export async function hook(opts) {
|
|
|
464
479
|
process.stdout.write(`${JSON.stringify(pass())}\n`);
|
|
465
480
|
return;
|
|
466
481
|
}
|
|
482
|
+
const { said, block } = await checkChanged(root, changed, mode, "command");
|
|
483
|
+
/**
|
|
484
|
+
* E O RELÓGIO ANDA AQUI, depois de olhar - `appendEvent` já o moveu para cada arquivo relatado,
|
|
485
|
+
* e esta linha cobre o caso em que todos os relatórios falharam na leitura.
|
|
486
|
+
*/
|
|
487
|
+
await startClock(root);
|
|
488
|
+
const context = said.join("\n\n");
|
|
489
|
+
process.stdout.write(`${JSON.stringify(said.length === 0 ? pass() : block ? refuse(context) : speak(context))}\n`);
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* O QUE A RODADA MUDOU, CHECADO - o corpo que era só do caminho de shell, e que agora o fim do turno
|
|
493
|
+
* também usa. Os dois fazem a mesma pergunta ("o que foi escrito desde a última vez que olhei?"),
|
|
494
|
+
* e duas cópias da resposta divergiriam no primeiro conserto.
|
|
495
|
+
*/
|
|
496
|
+
async function checkChanged(root, changed, mode,
|
|
497
|
+
/** O que a pessoa chama de rodada: o comando de shell, ou o turno inteiro do agente. */
|
|
498
|
+
round) {
|
|
467
499
|
const said = [];
|
|
468
500
|
/**
|
|
469
501
|
* UMA RECUSA EM QUALQUER ARQUIVO DA RODADA RECUSA A RODADA - e é o lado certo: um comando de
|
|
@@ -526,17 +558,61 @@ export async function hook(opts) {
|
|
|
526
558
|
}
|
|
527
559
|
if (rest.length > 0)
|
|
528
560
|
said.push([
|
|
529
|
-
`${rest.length} more file${rest.length === 1 ? "" : "s"} changed in this
|
|
561
|
+
`${rest.length} more file${rest.length === 1 ? "" : "s"} changed in this ${round} and ${rest.length === 1 ? "was" : "were"} not checked here: ${rest
|
|
530
562
|
.slice(0, 10)
|
|
531
563
|
.map((c) => c.rel)
|
|
532
564
|
.join(", ")}${rest.length > 10 ? `, +${rest.length - 10} more` : ""}.`,
|
|
533
|
-
|
|
565
|
+
round === "turn"
|
|
566
|
+
? "Run `npx synthesisui doctor` to see them - they are checked again the next turn one of them changes."
|
|
567
|
+
: "Run `npx synthesisui doctor` to see them, or write one of them again and this will check it.",
|
|
534
568
|
].join("\n"));
|
|
569
|
+
return { said, block };
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* O FIM DO TURNO - E01 da jornada "a checagem no fim do turno", escolha dele em 23/09: *"no fim de
|
|
573
|
+
* cada turno do agente, sobre o diff"*.
|
|
574
|
+
*
|
|
575
|
+
* UMA VOZ SÓ, E ELA SEGURA O TURNO. No `Stop`, a única saída que chega ao agente é `decision:
|
|
576
|
+
* "block"` com o motivo: o Claude Code devolve o motivo ao agente, e ele continua trabalhando em vez
|
|
577
|
+
* de encerrar. É o momento que a checagem por escrita não tinha - tudo o que o turno escreveu, numa
|
|
578
|
+
* mensagem só, antes de a resposta sair.
|
|
579
|
+
*
|
|
580
|
+
* E SEGURA UMA VEZ. Quando o turno já foi segurado, o Claude Code manda `stop_hook_active: true`, e
|
|
581
|
+
* este hook passa sem olhar. Sem isso, uma cor antiga que continua no arquivo - as do Hero do
|
|
582
|
+
* CodeLevel, medidas em 23/09 - prenderia o agente num laço que só a pessoa quebra.
|
|
583
|
+
*
|
|
584
|
+
* O RELÓGIO NÃO ANDA NESSA PASSAGEM, de propósito: o que o agente mudou para responder ao motivo
|
|
585
|
+
* fica mais novo que o relógio, e o próximo turno de verdade o checa. É assim que o ledger ganha a
|
|
586
|
+
* linha limpa que prova o conserto.
|
|
587
|
+
*/
|
|
588
|
+
async function endOfTurn(root, input) {
|
|
535
589
|
/**
|
|
536
|
-
*
|
|
537
|
-
*
|
|
590
|
+
* O SYNC, QUANDO ELA DISSE SIM - E03. Disparado antes de qualquer resposta e sem esperar: o turno
|
|
591
|
+
* não fica mais lento por causa da rede, e um sync que falha não custa a checagem. Sem o sim, esta
|
|
592
|
+
* linha não chega a tocar em rede nenhuma (ver `auto-sync.ts`).
|
|
538
593
|
*/
|
|
539
|
-
await
|
|
540
|
-
|
|
541
|
-
process.stdout.write(`${JSON.stringify(
|
|
594
|
+
if ((await readAutoSync(root).catch(() => null)) === true)
|
|
595
|
+
await startSync(root).catch(() => false);
|
|
596
|
+
const out = (frame) => process.stdout.write(`${JSON.stringify(frame)}\n`);
|
|
597
|
+
if (input.stop_hook_active) {
|
|
598
|
+
out(pass());
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const clock = await turnClockOf(root);
|
|
602
|
+
/** Ver `startClock`: o primeiro fim de turno depois de instalado acerta o relógio e cala. */
|
|
603
|
+
if (clock === 0) {
|
|
604
|
+
await moveTurnClock(root);
|
|
605
|
+
out(pass());
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
const ungoverned = await ungovernedIn(root);
|
|
609
|
+
const mode = await readMode(root).catch(() => null);
|
|
610
|
+
const changed = await changedSince(root, clock, (rel) => governs(rel, ungoverned)).catch(() => []);
|
|
611
|
+
const { said } = changed.length > 0
|
|
612
|
+
? await checkChanged(root, changed, mode, "turn")
|
|
613
|
+
: { said: [] };
|
|
614
|
+
await moveTurnClock(root);
|
|
615
|
+
out(said.length === 0
|
|
616
|
+
? pass()
|
|
617
|
+
: { decision: "block", reason: said.join("\n\n") });
|
|
542
618
|
}
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -171,7 +171,7 @@ async function rewireIfBehind(root, cli) {
|
|
|
171
171
|
mcp: true,
|
|
172
172
|
agents: await agentsToMaintain(root),
|
|
173
173
|
}).catch(() => null);
|
|
174
|
-
console.log(`↻ the check
|
|
174
|
+
console.log(`↻ the end-of-turn check moved from ${pinned} to ${cli}`);
|
|
175
175
|
console.log(" Reopen your editor session - hooks are read at startup.");
|
|
176
176
|
}
|
|
177
177
|
/**
|
|
@@ -32,7 +32,12 @@ register("pt-BR", {
|
|
|
32
32
|
// ── o estado do repositório ──
|
|
33
33
|
"· no design system installed here yet - the agent has no index, and memory has nothing to belong to.": "· nenhum design system instalado aqui ainda - o agente não tem índice, e a memória não tem a que pertencer.",
|
|
34
34
|
// ── a fiação do agente ──
|
|
35
|
-
"✓ .claude/settings.json the check now runs
|
|
35
|
+
"✓ .claude/settings.json the check now runs at the end of each agent turn": "✓ .claude/settings.json a verificação roda no fim de cada turno do agente",
|
|
36
|
+
"✓ .claude/settings.json the check moved from every write to the end of each agent turn": "✓ .claude/settings.json a verificação saiu de cada escrita e foi para o fim de cada turno do agente",
|
|
37
|
+
"✓ what the check records is sent to your dashboard at the end of each turn": "✓ o que a verificação registra vai para o seu painel no fim de cada turno",
|
|
38
|
+
"· what the check records stays on this machine - run `npx synthesisui sync` to send it": "· o que a verificação registra fica nesta máquina - rode `npx synthesisui sync` para enviar",
|
|
39
|
+
"send what the check records to your dashboard at the end of each turn?": "enviar o que a verificação registra para o seu painel no fim de cada turno?",
|
|
40
|
+
"· Codex has no end-of-turn hook we can hold - run `npx synthesisui doctor` to check what it wrote": "· o Codex não tem um fim de turno que a gente possa segurar - rode `npx synthesisui doctor` para checar o que ele escreveu",
|
|
36
41
|
"· .claude/settings.json already had it": "· .claude/settings.json já estava lá",
|
|
37
42
|
"✓ .claude/settings.json each session opens with what is missing": "✓ .claude/settings.json cada sessão abre com o que falta",
|
|
38
43
|
"✓ .claude/settings.json the session check moved to this one": "✓ .claude/settings.json a verificação de sessão veio para esta",
|
|
@@ -53,7 +58,7 @@ register("pt-BR", {
|
|
|
53
58
|
"Selected: {agents}": "Selecionados: {agents}",
|
|
54
59
|
" our part in {files} sits between `synthesisui:start` and `synthesisui:end` - the rest of each file is yours": " a nossa parte em {files} fica entre `synthesisui:start` e `synthesisui:end` - o resto de cada arquivo é seu",
|
|
55
60
|
" in {files} only our `synthesisui` entry is ours - the rest of the file is yours": " em {files} só a nossa entrada `synthesisui` é nossa - o resto do arquivo é seu",
|
|
56
|
-
" the
|
|
61
|
+
" the turn check inside it keeps running until that entry is gone": " a verificação do fim do turno dentro dele continua rodando até aquela entrada sair",
|
|
57
62
|
" the rules file is ours to delete; in `.cursor/mcp.json` only the `synthesisui` entry is - the other servers there are yours": " o arquivo de regras é nosso para apagar; no `.cursor/mcp.json` só a entrada `synthesisui` é - os outros servidores ali são seus",
|
|
58
63
|
"· not an agent this CLI knows: {names}": "· não é um agente que este CLI conhece: {names}",
|
|
59
64
|
"· no agent chosen, and nothing here says which agent you use - run with `--agents claude` or `--agents codex`, or run this in a terminal to pick from the list": "· nenhum agente escolhido, e nada aqui diz qual agente você usa - rode com `--agents claude` ou `--agents codex`, ou rode isto num terminal para escolher da lista",
|
|
@@ -117,12 +122,12 @@ register("pt-BR", {
|
|
|
117
122
|
"then, to pick up where you left off:": "e depois, para voltar de onde você parou:",
|
|
118
123
|
// ── os nomes do que moveu, usados dentro da frase acima ──
|
|
119
124
|
"the checks": "as verificações",
|
|
120
|
-
"the
|
|
125
|
+
"the turn check": "a verificação do fim do turno",
|
|
121
126
|
"the session check": "a verificação de sessão",
|
|
122
127
|
"the tools": "as ferramentas",
|
|
123
128
|
"the skills": "as skills",
|
|
124
129
|
// ── o custo do hook ──
|
|
125
|
-
"The hook resolves against the npm registry
|
|
130
|
+
"The hook resolves against the npm registry at the end of each turn - that is the wait, not the check. Add synthesisui to your devDependencies and run this again; it switches by itself to local, several times faster.": "O hook resolve no registro do npm no fim de cada turno - a espera é isso, não a verificação. Adicione synthesisui às suas devDependencies e rode isto de novo; ele passa a resolver local sozinho, várias vezes mais rápido.",
|
|
126
131
|
// ── até onde a garantia vai (uma vez, e de novo só quando a resposta muda) ──
|
|
127
132
|
"How far this check goes here": "Até onde esta verificação vai aqui",
|
|
128
133
|
"Every file this project writes is checked as it is written - by an edit tool or by a shell command, it makes no difference.": "Todo arquivo que este projeto escreve é verificado no momento da escrita - por uma ferramenta de edição ou por um comando de shell, dá no mesmo.",
|
|
@@ -130,8 +135,8 @@ register("pt-BR", {
|
|
|
130
135
|
"What a file no longer has is measured against your last commit. A file git has never seen has no before, so nothing is reported as removed there.": "O que um arquivo deixou de ter é medido contra o seu último commit. Um arquivo que o git nunca viu não tem um antes, então nada é reportado como removido nele.",
|
|
131
136
|
"A value counts as coming from your system in two spellings: `var(--token)`, and the {n} utility names your own theme generates - `bg-primary`, `p-6`, `rounded-lg`.": "Um valor conta como vindo do seu sistema em duas grafias: `var(--token)`, e os {n} nomes de utility que o seu próprio tema gera - `bg-primary`, `p-6`, `rounded-lg`.",
|
|
132
137
|
"A value counts as coming from your system when it is written as `var(--token)`. This system declares no theme names, so a utility class is not read as a reference to it.": "Um valor conta como vindo do seu sistema quando é escrito como `var(--token)`. Este sistema não declara nomes de tema, então uma classe utility não é lida como referência a ele.",
|
|
133
|
-
"Every file in this project is checked
|
|
134
|
-
"Every file in this project is checked
|
|
138
|
+
"Every file in this project is checked at the end of each agent turn, except {out}, which you named under `ungoverned` in `_synthesisui/config.json`.": "Todo arquivo deste projeto é verificado no fim de cada turno do agente, menos {out}, que você escreveu em `ungoverned` no `_synthesisui/config.json`.",
|
|
139
|
+
"Every file in this project is checked at the end of each agent turn. To leave a place out, name it under `ungoverned` in `_synthesisui/config.json`.": "Todo arquivo deste projeto é verificado no fim de cada turno do agente. Para deixar um lugar de fora, escreva-o em `ungoverned` no `_synthesisui/config.json`.",
|
|
135
140
|
"What this system knows was read from {scope}. {apps} declare a dependency on `{specifier}`, so they write in its vocabulary - nothing in them was read.": "O que este sistema sabe foi lido de {scope}. {apps} declaram dependência de `{specifier}`, então escrevem no vocabulário dele - nada dentro deles foi lido.",
|
|
136
141
|
"What this system knows was read from {scope} - nothing outside it was opened.": "O que este sistema sabe foi lido de {scope} - nada fora dali foi aberto.",
|
|
137
142
|
"A value this ruler cannot read is not a value it approved - silence here is never a pass.": "Um valor que esta régua não consegue ler não é um valor que ela aprovou - silêncio aqui nunca é aprovação.",
|
package/dist/guarantee.js
CHANGED
|
@@ -120,10 +120,10 @@ system) {
|
|
|
120
120
|
*/
|
|
121
121
|
lines.push(out.length > 0
|
|
122
122
|
? {
|
|
123
|
-
text: "Every file in this project is checked
|
|
123
|
+
text: "Every file in this project is checked at the end of each agent turn, except {out}, which you named under `ungoverned` in `_synthesisui/config.json`.",
|
|
124
124
|
values: { out: out.map((p) => `\`${p}\``).join(", ") },
|
|
125
125
|
}
|
|
126
|
-
: plain("Every file in this project is checked
|
|
126
|
+
: plain("Every file in this project is checked at the end of each agent turn. To leave a place out, name it under `ungoverned` in `_synthesisui/config.json`."));
|
|
127
127
|
/**
|
|
128
128
|
* E DE ONDE O SISTEMA FOI LIDO - outra pergunta, e por isso outra frase.
|
|
129
129
|
*
|
package/dist/install-marks.js
CHANGED
|
@@ -235,7 +235,24 @@
|
|
|
235
235
|
* chama `wireAgent`, então rodar o comando é o caminho de volta - e ele só alarga a string que era
|
|
236
236
|
* nossa, nunca um filtro que uma pessoa escreveu.
|
|
237
237
|
*/
|
|
238
|
-
|
|
238
|
+
/**
|
|
239
|
+
* 0.16.429 -> 0.16.453 em 23/09, e o passo 1 dá **SIM**: muda um byte que cai no `.claude/settings.json`
|
|
240
|
+
* dele. O hook sai do `PostToolUse` e vai para o `Stop` - a checagem passa a rodar no fim do turno do
|
|
241
|
+
* agente (E02 da jornada "a checagem no fim do turno"). Quem conectou antes tem o hook por escrita, e
|
|
242
|
+
* sem rodar o comando fica nele: a checagem continua a cada arquivo, e o fim do turno nunca é
|
|
243
|
+
* checado. É o mesmo caso de 12/09 acima: o `upgrade` chama `wireAgent`, e rodá-lo é o caminho.
|
|
244
|
+
*/
|
|
245
|
+
/**
|
|
246
|
+
* 0.16.453 -> 0.16.454, ainda em 23/09, e o passo 1 dá **SIM** outra vez: o `.gitignore` da pasta dele
|
|
247
|
+
* ganha `.auto-sync`, `.sync-started` e `.turn-clock` (E03). Sem o comando, o relógio do fim do turno
|
|
248
|
+
* aparece no `git status` dele no primeiro turno - um arquivo nosso que ele commitaria por engano.
|
|
249
|
+
*/
|
|
250
|
+
/**
|
|
251
|
+
* 0.16.454 -> 0.16.455, ainda em 23/09, e o passo 1 dá **SIM**: o bloco gerenciado do `CLAUDE.md` dele
|
|
252
|
+
* dizia que o hook "reports on every file you write" - a instrução que o agente dele lê sobre o próprio
|
|
253
|
+
* mecanismo. Passa a dizer o fim do turno (E04), e só o `upgrade` reescreve o bloco.
|
|
254
|
+
*/
|
|
255
|
+
export const MATERIALISER_SINCE = "0.16.455";
|
|
239
256
|
/**
|
|
240
257
|
* A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
|
|
241
258
|
*
|
|
@@ -366,7 +383,21 @@ export const COUNTED_DIFFERENTLY = "this run counts a value as named only when Y
|
|
|
366
383
|
* utility, um arquivo escrito inteiro no vocabulário do sistema recebia zero. A contagem em si é a
|
|
367
384
|
* outra marca - ver `COUNTED_DIFFERENTLY_SINCE`, que sobe no mesmo diff.
|
|
368
385
|
*/
|
|
369
|
-
|
|
386
|
+
/**
|
|
387
|
+
* 0.16.424 -> 0.16.454 em 23/09, e o passo 1 dá **SIM**: o hook passa a disparar o `sync` no fim do
|
|
388
|
+
* turno quando ela disse sim no `connect` (E03 da jornada "a checagem no fim do turno"). Um hook
|
|
389
|
+
* anterior - uma instalação local mais velha que o `connect` que fez a pergunta - lê o mesmo arquivo
|
|
390
|
+
* e não faz nada com ele: o painel continuaria atrasado com o sim dado. É o interruptor que parece
|
|
391
|
+
* ligado e não está, o mesmo caso do CONSUMIR em 12/09.
|
|
392
|
+
*/
|
|
393
|
+
/**
|
|
394
|
+
* 0.16.454 -> 0.16.456 em 27/09, e o passo 1 dá **SIM**: o envio do fim do turno passa a ser só o
|
|
395
|
+
* registro (`sync --record-only`), sem remedir nem mandar o censo - jornada "o sistema só muda com
|
|
396
|
+
* commit", E01. Um hook anterior, fixado na 0.16.454 ou 0.16.455, continua mandando o censo a cada
|
|
397
|
+
* turno, e o censo chama o Autopilot: trabalho pela metade pode virar versão. Quem está atrás precisa
|
|
398
|
+
* saber.
|
|
399
|
+
*/
|
|
400
|
+
export const CHECKER_SINCE = "0.16.456";
|
|
370
401
|
/**
|
|
371
402
|
* A ÚLTIMA VERSÃO EM QUE OS LEITORES PASSARAM A PRODUZIR UM CENSO DIFERENTE.
|
|
372
403
|
*
|
package/dist/report-insight.js
CHANGED
|
@@ -158,7 +158,7 @@ export function oQueMuda(c) {
|
|
|
158
158
|
const aMao = observados.filter((v) => !v.token);
|
|
159
159
|
if (aMao.length === 0)
|
|
160
160
|
return [
|
|
161
|
-
"Every design value in your code already comes from a token you declare. What SynthesisUI adds here is keeping it that way: the check runs
|
|
161
|
+
"Every design value in your code already comes from a token you declare. What SynthesisUI adds here is keeping it that way: the check runs at the end of every agent turn, and a value that drifts off your own scale comes back to you as a decision instead of landing silently.",
|
|
162
162
|
];
|
|
163
163
|
const repetidos = aMao.filter((v) => v.count > 1);
|
|
164
164
|
const usos = repetidos.reduce((n, v) => n + v.count, 0);
|
|
@@ -166,7 +166,7 @@ export function oQueMuda(c) {
|
|
|
166
166
|
linhas.push(`${aMao.length} of your design values are written by hand today. Importing gives each one a name in your own vocabulary, so changing it later means changing it once instead of finding every copy.`);
|
|
167
167
|
if (repetidos.length > 0)
|
|
168
168
|
linhas.push(`${repetidos.length} of those are repeated - ${usos} uses across your files. Every copy is a place the next change can be forgotten, which is how two screens end up almost the same colour.`);
|
|
169
|
-
linhas.push("And it stops coming back: once the system is in the repository, the check runs
|
|
169
|
+
linhas.push("And it stops coming back: once the system is in the repository, the check runs at the end of every agent turn and catches a value that drifts off your own scale, before it ships.");
|
|
170
170
|
return linhas;
|
|
171
171
|
}
|
|
172
172
|
/**
|
|
@@ -193,7 +193,7 @@ export function passos() {
|
|
|
193
193
|
{
|
|
194
194
|
comando: "npx synthesisui@latest connect",
|
|
195
195
|
titulo: "Wire your agent",
|
|
196
|
-
porque: "Installs the check that runs
|
|
196
|
+
porque: "Installs the check that runs at the end of every agent turn, the tools it can query, and the skills that know this pipeline. Nothing of your code is touched or sent. Run it before you open the agent - a session only picks these up when it starts.",
|
|
197
197
|
},
|
|
198
198
|
{
|
|
199
199
|
comando: "npx synthesisui@latest import",
|
package/package.json
CHANGED