synthesisui 0.16.483 → 0.16.485
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/baseline-craft.js +13 -1
- package/dist/claude-md.js +19 -5
- package/dist/commands/add.js +69 -45
- package/dist/commands/component.js +38 -0
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/hook.js +55 -5
- package/dist/commands/import.js +162 -5
- package/dist/commands/init.js +10 -0
- package/dist/commands/sync.js +50 -2
- package/dist/doctor/architecture.js +4 -1
- package/dist/doctor/broken-refs.js +61 -0
- package/dist/doctor/component-gate.js +14 -0
- package/dist/doctor/components-scan.js +9 -0
- package/dist/doctor/other-frameworks.js +26 -0
- package/dist/doctor/scan.js +82 -3
- package/dist/doctor/scheme-scope.js +39 -1
- package/dist/doctor/sheet-order.js +99 -0
- package/dist/doctor/ts-tokens-lens.js +143 -21
- package/dist/install-marks.js +14 -2
- package/dist/merge-census.js +7 -0
- package/dist/responsive-form.js +85 -0
- package/package.json +1 -1
package/dist/baseline-craft.js
CHANGED
|
@@ -21,6 +21,16 @@ import { body, bodyWrapped, paint } from "./output.js";
|
|
|
21
21
|
* Vazio e não uma linha vazia: quem chama decide se imprime o título, e um bloco com cabeçalho e
|
|
22
22
|
* nada embaixo é pior que bloco nenhum.
|
|
23
23
|
*/
|
|
24
|
+
/** "A and B", "A, B, C, D and 2 more" - quatro nomes cabem numa linha de terminal. */
|
|
25
|
+
function namesOf(names) {
|
|
26
|
+
const shown = names.slice(0, 4);
|
|
27
|
+
const rest = names.length - shown.length;
|
|
28
|
+
if (rest > 0)
|
|
29
|
+
return `${shown.join(", ")} and ${rest} more`;
|
|
30
|
+
return shown.length === 1
|
|
31
|
+
? shown[0]
|
|
32
|
+
: `${shown.slice(0, -1).join(", ")} and ${shown[shown.length - 1]}`;
|
|
33
|
+
}
|
|
24
34
|
export function craftLines(craft) {
|
|
25
35
|
if (!craft)
|
|
26
36
|
return [];
|
|
@@ -50,7 +60,9 @@ export function craftLines(craft) {
|
|
|
50
60
|
* a segunda pergunta, e ela merece a segunda linha.
|
|
51
61
|
*/
|
|
52
62
|
if (apart)
|
|
53
|
-
lines.push(...bodyWrapped(
|
|
63
|
+
lines.push(...bodyWrapped(craft.unwrittenNames?.length
|
|
64
|
+
? `${namesOf(craft.unwrittenNames)} have no recipe yet - nothing in their files paints them - so they are counted apart, never against you.`
|
|
65
|
+
: `${apart} have no recipe yet: counted apart, never against you.`).map((l) => body(paint.dim(l))));
|
|
54
66
|
/**
|
|
55
67
|
* OS RÓTULOS ALINHADOS PELO MAIS LONGO, e a largura é medida e não estimada: os nomes chegam do
|
|
56
68
|
* servidor, então um padding fixo desalinharia no dia em que uma lente for renomeada lá.
|
package/dist/claude-md.js
CHANGED
|
@@ -3,6 +3,7 @@ import { dirname, join } from "node:path";
|
|
|
3
3
|
import { hasHook } from "./agent-wiring.js";
|
|
4
4
|
import { declaredReference } from "./group-role.js";
|
|
5
5
|
import { recallAvailable } from "./memory/availability.js";
|
|
6
|
+
import { readResponsiveForm, responsiveLine } from "./responsive-form.js";
|
|
6
7
|
const START = "<!-- synthesisui:start -->";
|
|
7
8
|
const END = "<!-- synthesisui:end -->";
|
|
8
9
|
/** Reads the installed DSs from the .lock files in _synthesisui/ds/<slug>/. */
|
|
@@ -454,12 +455,19 @@ and task, call it once - an empty result means there is nothing more to retrieve
|
|
|
454
455
|
tells you what was decided and why it matters; **the file on disk is still the authority for what is
|
|
455
456
|
written now** - read the code before you edit it, never a remembered signature.`
|
|
456
457
|
: "";
|
|
458
|
+
/**
|
|
459
|
+
* O TEMA É DELE - bateria ampliada, item 22 (29/09). Em c01, c02, c05, c17 e c23 o agente mexeu no tema do projeto
|
|
460
|
+
* sem perguntar: `--text-xl` no globals.css para "casar com a escala", 17 valores no tailwind.config.js, três cores
|
|
461
|
+
* de status no custom.scss, um `app/tokens.css` com os nomes `--ds-*` da plataforma. Cada um parecia ajudar, e cada
|
|
462
|
+
* um é uma decisão da pessoa tomada por outra pessoa.
|
|
463
|
+
*/
|
|
464
|
+
const theirsLine = "\n\n**The project's theme is the person's.** Do not add, change or remove a token, a theme config value (tailwind.config, theme.ts, uno.config) or the stylesheet that declares them unless they ask. A value you need and the theme does not have is ONE question with your proposal - not an edit.";
|
|
457
465
|
const rule = recall.available
|
|
458
466
|
? onlyAdopted
|
|
459
467
|
? `**These are true without asking anyone:** use the project's OWN custom properties, exactly as
|
|
460
468
|
this system declares them. No raw colours, spacings or radii that a token already covers, and never a
|
|
461
469
|
new token invented in silence - say so instead, because a new token is a decision for a person. There
|
|
462
|
-
is no component index for an adopted system: the tokens ARE the contract
|
|
470
|
+
is no component index for an adopted system: the tokens ARE the contract.${theirsLine}
|
|
463
471
|
|
|
464
472
|
**What the vocabulary IS is not written here.** Fetch only the piece the task requires:
|
|
465
473
|
\`system_doctrine\` for the rules and the voice, \`find_token\` for a value you are about to write.${memoryLine}${selfCheck}`
|
|
@@ -471,7 +479,7 @@ is no component index for an adopted system: the tokens ARE the contract.
|
|
|
471
479
|
- To override a style a component already sets, use this system's semantic role class - never \`!\`.
|
|
472
480
|
If the override is ignored, regenerate that component: older ones predate the resolver.
|
|
473
481
|
- Motion is selection, not improvisation. The base is quiet: nothing moves until a person asks, and
|
|
474
|
-
what moves comes from this system's own vocabulary - never hand-rolled \`@keyframes\` or raw durations
|
|
482
|
+
what moves comes from this system's own vocabulary - never hand-rolled \`@keyframes\` or raw durations.${theirsLine}
|
|
475
483
|
|
|
476
484
|
**When you are writing UI that belongs to one of the systems below, check its index first.** If an
|
|
477
485
|
entry covers the purpose, do not write it from scratch - materialise it:
|
|
@@ -491,11 +499,11 @@ If nothing covers it, say which indexed entry you considered and why it did not
|
|
|
491
499
|
project's OWN custom properties, exactly as the guide lists them. Do not write raw colours,
|
|
492
500
|
spacings or radii that a token already covers, and do not invent a new token silently - say so
|
|
493
501
|
instead, because a new token is a decision for a person to make. There is no component
|
|
494
|
-
manifest for an adopted system - the tokens ARE the contract.${selfCheck}`
|
|
502
|
+
manifest for an adopted system - the tokens ARE the contract.${theirsLine}${selfCheck}`
|
|
495
503
|
: `**When creating or editing components, read the system's GUIDE.md and follow it:** use the
|
|
496
504
|
vocabulary it lists - every name in it is a name THIS project already declares, written exactly as
|
|
497
505
|
you would write it. Do not use raw values outside the system's scale, and never a name from a
|
|
498
|
-
stylesheet this project does not have
|
|
506
|
+
stylesheet this project does not have.${theirsLine}
|
|
499
507
|
|
|
500
508
|
**Before creating any UI element, look it up in the manifest below.** If something there
|
|
501
509
|
already covers the purpose, do not write it from scratch - run
|
|
@@ -543,9 +551,15 @@ element. Write every user-facing string in that language - labels, empty states,
|
|
|
543
551
|
\`alt\`. A screen reader pronounces \`aria-label\` using \`lang\`, so a mixed-language interface is
|
|
544
552
|
worse than an untranslated one. If the attribute is wrong, change it rather than writing against
|
|
545
553
|
it.`;
|
|
554
|
+
/**
|
|
555
|
+
* E COMO ELE FAZ O RESPONSIVO, medido como o idioma (item 19 da bateria, 29/09): no b9, 100% inline, o agente criou
|
|
556
|
+
* CSS Modules só para o ponto de quebra, porque ninguém tinha dito como aquele projeto faz.
|
|
557
|
+
*/
|
|
558
|
+
const form = await readResponsiveForm(projectRoot).catch(() => null);
|
|
559
|
+
const responsive = form ? `\n\n${responsiveLine(form)}` : "";
|
|
546
560
|
const body = `## Design Systems (via SynthesisUI)
|
|
547
561
|
|
|
548
|
-
This project uses design system(s) tracked by the \`synthesisui\` CLI. ${rule}${language}
|
|
562
|
+
This project uses design system(s) tracked by the \`synthesisui\` CLI. ${rule}${language}${responsive}
|
|
549
563
|
|
|
550
564
|
${sections.join("\n")}
|
|
551
565
|
|
package/dist/commands/add.js
CHANGED
|
@@ -870,56 +870,80 @@ export async function add(slug, opts) {
|
|
|
870
870
|
* `fonts.ts`. Um arquivo compartilhado atravessaria a fronteira do workspace.
|
|
871
871
|
*/
|
|
872
872
|
const wroteIn = [];
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
const snippet = plan;
|
|
889
|
-
const header = [
|
|
890
|
-
`// Self-hosted type for the "${payload.slug}" design system (via next/font -`,
|
|
891
|
-
`// preloaded, no font flash). Generated by \`synthesisui add\`; edit freely.`,
|
|
892
|
-
];
|
|
893
|
-
await writeFile(fontsPath, `${[...header, ...snippet.fontsFile.slice(1)].join("\n")}\n`, "utf8");
|
|
894
|
-
wroteIn.push(`${dir}/fonts.ts`);
|
|
895
|
-
}
|
|
896
|
-
const wroteFonts = wroteIn.length > 0;
|
|
897
|
-
console.log("");
|
|
898
|
-
if (wroteFonts) {
|
|
899
|
-
console.log(line(`3. ✓ wrote ${wroteIn.join(", ")} - self-hosted type via next/font (preloaded, no font flash).`));
|
|
900
|
-
console.log(line(" Finish the wiring with two small edits:"));
|
|
901
|
-
}
|
|
902
|
-
else {
|
|
903
|
-
console.log(line(appDirFound
|
|
904
|
-
? `3. Load the type via next/font (${appDir}/fonts.ts already exists - left untouched; it should export:)`
|
|
905
|
-
: /**
|
|
906
|
-
* A FRASE DIZIA "already exists" PARA DOIS ESTADOS DIFERENTES, e um deles é o contrário
|
|
907
|
-
* disso: não achei a pasta do app. No monorepo dele o arquivo não existia e o terminal
|
|
908
|
-
* dizia que existia - o cliente não tem como saber que os caminhos acima são chute.
|
|
909
|
-
*/
|
|
910
|
-
`3. Load the type via next/font (I could not find your app folder from here, so the paths above are examples - create ${appDir}/fonts.ts wherever your app router lives):`));
|
|
873
|
+
/**
|
|
874
|
+
* NO IMPORT, NENHUM ARQUIVO MORTO - bateria ampliada, item 20 (29/09). O c01 declara Inter e Space Grotesk e não as
|
|
875
|
+
* carrega; o import escrevia `app/fonts.ts` e deixava "two small edits" que ninguém fez - um arquivo que nada
|
|
876
|
+
* importa, e as fontes do sistema sem aparecer na tela. No projeto de origem o código é dele: o passo diz em uma
|
|
877
|
+
* linha o que acontece hoje e mostra a ligação inteira, para ele fazer (ou pedir ao agente) se quiser.
|
|
878
|
+
*/
|
|
879
|
+
const named = [
|
|
880
|
+
...new Set(Object.values(families)
|
|
881
|
+
.filter((f) => typeof f === "string")
|
|
882
|
+
.map((f) => f.split(",")[0]?.trim().replace(/^["']|["']$/g, "") ?? "")
|
|
883
|
+
.filter(Boolean)),
|
|
884
|
+
];
|
|
885
|
+
if (opts.fromImport) {
|
|
886
|
+
console.log("");
|
|
887
|
+
console.log(line(`3. Your code names ${named.join(" and ") || "its fonts"} and nothing here loads ${named.length === 1 ? "it" : "them"} - today the text falls back to the browser's font. Nothing was written. To load ${named.length === 1 ? "it" : "them"}, these three edits do it:`));
|
|
911
888
|
console.log("");
|
|
912
889
|
console.log(snippet(nextFonts.fontsFile));
|
|
913
890
|
console.log("");
|
|
914
|
-
console.log(
|
|
891
|
+
console.log(snippet(nextFonts.layout));
|
|
892
|
+
console.log("");
|
|
893
|
+
console.log(snippet(nextFonts.css));
|
|
915
894
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
895
|
+
else {
|
|
896
|
+
for (const dir of appDirs.length > 0 ? appDirs : [appDir]) {
|
|
897
|
+
const fontsPath = join(projectRoot, ...dir.split("/"), "fonts.ts");
|
|
898
|
+
/** Arquivo dele que já existe nunca é reescrito - o setup impresso diz o que ele deve exportar. */
|
|
899
|
+
if (await exists(fontsPath))
|
|
900
|
+
continue;
|
|
901
|
+
if (!(await exists(join(projectRoot, ...dir.split("/")))))
|
|
902
|
+
continue;
|
|
903
|
+
const plan = nextFontSnippet({
|
|
904
|
+
families,
|
|
905
|
+
appDir: dir,
|
|
906
|
+
facts: fontFacts,
|
|
907
|
+
declaredWeights: payload.document.foundations.typography.weights,
|
|
908
|
+
});
|
|
909
|
+
if (!plan.ok)
|
|
910
|
+
continue;
|
|
911
|
+
const snippet = plan;
|
|
912
|
+
const header = [
|
|
913
|
+
`// Self-hosted type for the "${payload.slug}" design system (via next/font -`,
|
|
914
|
+
`// preloaded, no font flash). Generated by \`synthesisui add\`; edit freely.`,
|
|
915
|
+
];
|
|
916
|
+
await writeFile(fontsPath, `${[...header, ...snippet.fontsFile.slice(1)].join("\n")}\n`, "utf8");
|
|
917
|
+
wroteIn.push(`${dir}/fonts.ts`);
|
|
918
|
+
}
|
|
919
|
+
const wroteFonts = wroteIn.length > 0;
|
|
920
|
+
console.log("");
|
|
921
|
+
if (wroteFonts) {
|
|
922
|
+
console.log(line(`3. ✓ wrote ${wroteIn.join(", ")} - self-hosted type via next/font (preloaded, no font flash).`));
|
|
923
|
+
console.log(line(" Finish the wiring with two small edits:"));
|
|
924
|
+
}
|
|
925
|
+
else {
|
|
926
|
+
console.log(line(appDirFound
|
|
927
|
+
? `3. Load the type via next/font (${appDir}/fonts.ts already exists - left untouched; it should export:)`
|
|
928
|
+
: /**
|
|
929
|
+
* A FRASE DIZIA "already exists" PARA DOIS ESTADOS DIFERENTES, e um deles é o contrário
|
|
930
|
+
* disso: não achei a pasta do app. No monorepo dele o arquivo não existia e o terminal
|
|
931
|
+
* dizia que existia - o cliente não tem como saber que os caminhos acima são chute.
|
|
932
|
+
*/
|
|
933
|
+
`3. Load the type via next/font (I could not find your app folder from here, so the paths above are examples - create ${appDir}/fonts.ts wherever your app router lives):`));
|
|
934
|
+
console.log("");
|
|
935
|
+
console.log(snippet(nextFonts.fontsFile));
|
|
936
|
+
console.log("");
|
|
937
|
+
console.log(line(" Then finish the wiring:"));
|
|
938
|
+
}
|
|
939
|
+
console.log("");
|
|
940
|
+
console.log(snippet(nextFonts.layout));
|
|
921
941
|
console.log("");
|
|
922
|
-
console.log(
|
|
942
|
+
console.log(snippet(nextFonts.css));
|
|
943
|
+
if (fontsHref) {
|
|
944
|
+
console.log("");
|
|
945
|
+
console.log(line(` Quick alternative (works anywhere, may flash on cold loads): <link rel="stylesheet" href="${fontsHref}" /> in the <head>.`));
|
|
946
|
+
}
|
|
923
947
|
}
|
|
924
948
|
}
|
|
925
949
|
else if (fontsHref) {
|
|
@@ -77,6 +77,17 @@ export async function component(slug, name, opts) {
|
|
|
77
77
|
if (!SAFE_NAME.test(slug)) {
|
|
78
78
|
throw new RegistryError(`Invalid slug "${slug}".`);
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* FORA DE REACT, O COMANDO DIZ E PARA - bateria ampliada, item 15 (29/09). Em Astro, Svelte, Vue, Angular e num site
|
|
82
|
+
* HTML (c11 a c14, c28) ele escrevia um `.tsx` React, e o agente traduzia à mão: no Vue sobraram `cn.ts` e
|
|
83
|
+
* `sidebar.tsx` esquecidos no projeto. A promessa é React e Next; o que não cabe nela se diz antes de escrever.
|
|
84
|
+
*/
|
|
85
|
+
const other = await notReact(root);
|
|
86
|
+
if (other) {
|
|
87
|
+
console.log(`This project is written in ${other}, and \`synthesisui component\` writes React components, so nothing was written. Build it in ${other === "plain HTML" ? "your own HTML and CSS" : other} with the system's values: \`synthesisui use ${slug} "<what you want>"\` prints a prompt your agent can follow, and the MCP tools answer every value.`);
|
|
88
|
+
process.exitCode = 1;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
80
91
|
console.log(`→ fetching "${name}" from "${slug}" …`);
|
|
81
92
|
const res = await fetchComponent(base, slug, name, opts.version);
|
|
82
93
|
// The server should only ever return a kebab-case name, but never trust a
|
|
@@ -632,3 +643,30 @@ export async function component(slug, name, opts) {
|
|
|
632
643
|
}
|
|
633
644
|
console.log("");
|
|
634
645
|
}
|
|
646
|
+
/** O framework que não é React, pelo `package.json` - ou "plain HTML" quando não há `package.json` e há um `.html`. */
|
|
647
|
+
async function notReact(root) {
|
|
648
|
+
const raw = await readFile(join(root, "package.json"), "utf8").catch(() => null);
|
|
649
|
+
if (raw == null) {
|
|
650
|
+
const { readdir } = await import("node:fs/promises");
|
|
651
|
+
const names = await readdir(root).catch(() => []);
|
|
652
|
+
return names.some((n) => /\.html?$/i.test(n)) ? "plain HTML" : null;
|
|
653
|
+
}
|
|
654
|
+
try {
|
|
655
|
+
const pkg = JSON.parse(raw);
|
|
656
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
657
|
+
if (deps.react || deps.next || deps["react-dom"])
|
|
658
|
+
return null;
|
|
659
|
+
if (deps.nuxt || deps.vue)
|
|
660
|
+
return "Vue";
|
|
661
|
+
if (deps["@sveltejs/kit"] || deps.svelte)
|
|
662
|
+
return "Svelte";
|
|
663
|
+
if (deps.astro)
|
|
664
|
+
return "Astro";
|
|
665
|
+
if (deps["@angular/core"])
|
|
666
|
+
return "Angular";
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
catch {
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { readStyleSheet, scssVariables } from "../doctor/scss-lens.js";
|
|
3
|
-
import { tsTokenObjects, tsTokensAsVars } from "../doctor/ts-tokens-lens.js";
|
|
3
|
+
import { THEME_SOURCE, tsTokenObjects, tsTokensAsVars } from "../doctor/ts-tokens-lens.js";
|
|
4
4
|
import { dirname, join, relative, resolve } from "node:path";
|
|
5
5
|
import { describeIntent, intentOf, readProjectConfig } from "../config.js";
|
|
6
6
|
import { describeFix, planFix, readerFor, writeFix, } from "../doctor/apply-fix.js";
|
|
@@ -472,7 +472,7 @@ measured) {
|
|
|
472
472
|
/** E o objeto de tokens em TypeScript - ver `TokenTable.tsNames` (27/09). */
|
|
473
473
|
const tsNames = new Map();
|
|
474
474
|
for await (const file of walkAll(roots))
|
|
475
|
-
if (
|
|
475
|
+
if (THEME_SOURCE.test(file) && !/\.d\.ts$/.test(file))
|
|
476
476
|
for (const o of tsTokenObjects(await readFile(file, "utf8").catch(() => "")))
|
|
477
477
|
for (const [n, v] of o.values)
|
|
478
478
|
if (!tsNames.has(n))
|
|
@@ -774,7 +774,7 @@ export async function doctor(opts) {
|
|
|
774
774
|
/** Os objetos de tokens em TypeScript, para a lente da varredura - lidos da RAIZ, como o vocabulário. */
|
|
775
775
|
const tsObjects = [];
|
|
776
776
|
for await (const file of walkAll([root]))
|
|
777
|
-
if (
|
|
777
|
+
if (THEME_SOURCE.test(file) && !/\.d\.ts$/.test(file))
|
|
778
778
|
tsObjects.push(...tsTokenObjects(await readFile(file, "utf8").catch(() => "")));
|
|
779
779
|
const tally = emptyTally();
|
|
780
780
|
const internalSpecs = await internalSpecifiers(root);
|
package/dist/commands/hook.js
CHANGED
|
@@ -425,7 +425,14 @@ async function report(root, filePath, mode) {
|
|
|
425
425
|
/** O QUE FOI CORTADO É DITO - medido num `.css` real: 20 de 252, e as 232 sumiam caladas. */
|
|
426
426
|
...(unnamed.length > 20
|
|
427
427
|
? [` … and ${unnamed.length - 20} more in this file`]
|
|
428
|
-
: []), "", "This system declares no name for them, so there is nothing to replace them with.",
|
|
428
|
+
: []), "", "This system declares no name for them, so there is nothing to replace them with.",
|
|
429
|
+
/**
|
|
430
|
+
* UMA PERGUNTA COM A PROPOSTA, NÃO UMA LISTA PARA NOMEAR - bateria ampliada, item 18 (29/09). Em 17 dos 29
|
|
431
|
+
* projetos o turno terminava em "tell me what to call them" com 5 a 43 valores: a tela pronta e o trabalho
|
|
432
|
+
* aberto, esperando a pessoa inventar nomes. Quem escolhe continua sendo ela; o que muda é que ela recebe a
|
|
433
|
+
* sugestão pronta, no estilo que o projeto já usa, e responde uma vez.
|
|
434
|
+
*/
|
|
435
|
+
"Do not add a name on your own. For the values that repeat, propose names in this project's own style (next to the names it already declares) as ONE question the person answers with a yes - then add them and swap the values only after that yes. Leave one-off values as they are, and do not list every value back to the person.");
|
|
429
436
|
}
|
|
430
437
|
if (phantoms.length > 0) {
|
|
431
438
|
lines.push("", "Names this system does not declare. These look tokenized and apply nothing at all:", ...phantoms.slice(0, 20).map((p) => ` line ${p.line} ${p.name}`), "", "Use a name the system has, or say which value you need and what you would call it. Do NOT invent a token.");
|
|
@@ -613,9 +620,17 @@ round) {
|
|
|
613
620
|
if (src == null || !table)
|
|
614
621
|
continue;
|
|
615
622
|
measured += 1;
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
623
|
+
/**
|
|
624
|
+
* O QUE SAI DA ESCALA CONTA AQUI TAMBÉM - bateria ampliada, item 14 (29/09). No c06 o turno mudou 18 arquivos, e
|
|
625
|
+
* três deles tinham `@media (max-width: 767px)`, fora da escala (o md é 768). A régua de 28/09 achava; esta
|
|
626
|
+
* linha só somava os valores à mão, e o turno terminava em "checked too - nothing to name".
|
|
627
|
+
*/
|
|
628
|
+
const report = scanSource(rel, src, table);
|
|
629
|
+
const n = diagnose([report]).findings.length;
|
|
630
|
+
const off = report.offScale?.length ?? 0;
|
|
631
|
+
const what = [n > 0 ? `${n} by hand` : "", off > 0 ? `${off} off your scale` : ""].filter(Boolean).join(", ");
|
|
632
|
+
if (what)
|
|
633
|
+
dirty.push(`${rel} (${what})`);
|
|
619
634
|
}
|
|
620
635
|
const unmeasured = rest.length - measured;
|
|
621
636
|
said.push([
|
|
@@ -634,6 +649,34 @@ round) {
|
|
|
634
649
|
: "Run `npx synthesisui doctor` to see them, or write one of them again and this will check it.",
|
|
635
650
|
].join("\n"));
|
|
636
651
|
}
|
|
652
|
+
/**
|
|
653
|
+
* O QUE PASSOU DE PROPÓSITO, DITO UMA VEZ POR TURNO - a régua de 28/09 ("a checagem diz isso uma vez"), bateria
|
|
654
|
+
* ampliada, item 14. Sem esta linha, "nothing to name" ao lado de um `1px` ou de um `100dvh` parecia que a checagem
|
|
655
|
+
* não tinha visto, e o agente do c06 e do c18 foi conferir à mão. Só no fim do turno: um comando de shell não repete.
|
|
656
|
+
*/
|
|
657
|
+
// Só pega carona: um turno limpo continua calado - bloquear o fim do turno para dizer que passou seria ruído.
|
|
658
|
+
if (round === "turn" && changed.length > 0 && said.length > 0) {
|
|
659
|
+
const { table } = await loadSystem(root).catch(() => ({ table: null }));
|
|
660
|
+
const passes = new Map();
|
|
661
|
+
if (table)
|
|
662
|
+
for (const { rel } of changed.slice(0, REFUSAL_CEILING)) {
|
|
663
|
+
const src = await readFile(join(root, rel), "utf8").catch(() => null);
|
|
664
|
+
if (src == null)
|
|
665
|
+
continue;
|
|
666
|
+
for (const a of scanSource(rel, src, table).setAside ?? []) {
|
|
667
|
+
if (!/passes$|no token holds$|on your system's scale$/.test(a.reason))
|
|
668
|
+
continue;
|
|
669
|
+
const files = passes.get(a.reason) ?? new Set();
|
|
670
|
+
files.add(rel);
|
|
671
|
+
passes.set(a.reason, files);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (passes.size > 0)
|
|
675
|
+
said.push(`Passed on purpose, not missed: ${[...passes]
|
|
676
|
+
.slice(0, 6)
|
|
677
|
+
.map(([why, files]) => `${why} (${files.size} file${files.size === 1 ? "" : "s"})`)
|
|
678
|
+
.join("; ")}.`);
|
|
679
|
+
}
|
|
637
680
|
return { said, block };
|
|
638
681
|
}
|
|
639
682
|
/**
|
|
@@ -686,5 +729,12 @@ async function endOfTurn(root, input) {
|
|
|
686
729
|
await moveTurnClock(root);
|
|
687
730
|
out(said.length === 0
|
|
688
731
|
? pass()
|
|
689
|
-
: { decision: "block", reason: said.join("\n\n") });
|
|
732
|
+
: { decision: "block", reason: [...said, CLOSE_THE_TURN].join("\n\n") });
|
|
690
733
|
}
|
|
734
|
+
/**
|
|
735
|
+
* A ÚLTIMA MENSAGEM É O RESUMO, NÃO O RODAPÉ - bateria ampliada, item 19 (29/09). Este bloqueio faz o agente responder
|
|
736
|
+
* mais uma vez, e essa resposta vira a última coisa que a pessoa lê: em quase todos os 30 projetos, um turno de três a
|
|
737
|
+
* cinco minutos terminava numa nota sobre a checagem ("One more note from the style checker…"), e o que foi feito
|
|
738
|
+
* ficava no meio da conversa.
|
|
739
|
+
*/
|
|
740
|
+
export const CLOSE_THE_TURN = "Answer this in a line or two, then END your reply with the summary of what this turn did for the person - that last message is the one they read.";
|