synthesisui 0.16.336 → 0.16.338
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/import.js +83 -12
- package/dist/doctor/architecture.js +193 -14
- package/dist/install-marks.js +41 -1
- package/dist/merge-census.js +35 -0
- package/package.json +1 -1
package/dist/commands/import.js
CHANGED
|
@@ -5,7 +5,7 @@ import { applyAnatomyPatch, hasEdits, } from "../anatomy-patch.js";
|
|
|
5
5
|
import { resolveAnatomy, resolveFlatParts, safePartName, } from "../anatomy-read.js";
|
|
6
6
|
import { readCredentials, readToken, resolveRegistry, sameRegistry, } from "../config.js";
|
|
7
7
|
import { declaredElsewhere } from "../declared-elsewhere.js";
|
|
8
|
-
import { architectureRule, componentHome, describeArchitecture, describeChoice, detectArchitectures, homeLine, proposeNewHome, } from "../doctor/architecture.js";
|
|
8
|
+
import { architectureGap, architectureRule, componentHome, describeArchitecture, describeChoice, describeGap, detectArchitectures, homeLine, proposeNewHome, } from "../doctor/architecture.js";
|
|
9
9
|
import { findBrokenRefs } from "../doctor/broken-refs.js";
|
|
10
10
|
import { nestingRules, propRules, readDefinitionProps, readNesting, readRuntime, } from "../doctor/call-sites.js";
|
|
11
11
|
import { asCatalogueTable, describeFallback, fetchCatalogue, } from "../doctor/catalogue-fetch.js";
|
|
@@ -388,6 +388,21 @@ const MAX_GLOBAL_CLASSES = 300;
|
|
|
388
388
|
* dois com folga e continua sendo um teto de verdade, alinhado com `DISTINCT_CAP` do ledger.
|
|
389
389
|
*/
|
|
390
390
|
const SKIPPED_CAP = 4096;
|
|
391
|
+
/**
|
|
392
|
+
* UM ARQUIVO QUE MOSTRA UM COMPONENTE NÃO É UM COMPONENTE - a mesma exclusão nos três lugares.
|
|
393
|
+
*
|
|
394
|
+
* Stories and tests compose components to SHOW them; counting those as the product's own
|
|
395
|
+
* composition is the same lie the colour census told before they were set aside.
|
|
396
|
+
*
|
|
397
|
+
* E ela vale igual para a CONTAGEM que a organização declara. Medido em 30/08 em
|
|
398
|
+
* `codelevel-monorepo/packages/ui`: 75 arquivos `.tsx`, dos quais 38 são `*.stories.tsx` e 37 são
|
|
399
|
+
* componente. Dizer "we read 75 component files" a quem tem 37 é metade do número inventada por
|
|
400
|
+
* nós - e a frase existe justamente para o caso em que o cliente vai conferir o número na mão.
|
|
401
|
+
*
|
|
402
|
+
* UMA constante, e não três literais: a terceira cópia já existia antes desta, e uma delas
|
|
403
|
+
* envelhecer sozinha significa dois denominadores diferentes para a mesma palavra na mesma saída.
|
|
404
|
+
*/
|
|
405
|
+
const SHOWS_A_COMPONENT = /(\.(spec|test|stories)\.[a-z]+$|__tests__\/|(^|\/)\.storybook\/)/;
|
|
391
406
|
export async function takeCensus(root, opts) {
|
|
392
407
|
/**
|
|
393
408
|
* O RELATÓRIO SÓ SAI QUANDO ALGUÉM O PEDIU - `say` cala quando o chamador é uma RE-medição.
|
|
@@ -552,6 +567,8 @@ export async function takeCensus(root, opts) {
|
|
|
552
567
|
* naming how the project is organised costs no second pass. See `architecture.ts`.
|
|
553
568
|
*/
|
|
554
569
|
const dirs = new Map();
|
|
570
|
+
/** Uma entrada nova da árvore, com os três contadores em zero. */
|
|
571
|
+
const freshDir = () => ({ dirs: new Set(), files: 0, direct: 0 });
|
|
555
572
|
/** Parent → child → the files that put one inside the other. */
|
|
556
573
|
const nesting = new Map();
|
|
557
574
|
/**
|
|
@@ -621,17 +638,37 @@ export async function takeCensus(root, opts) {
|
|
|
621
638
|
continue;
|
|
622
639
|
const rel = relative(root, file);
|
|
623
640
|
progress.step(rel);
|
|
624
|
-
|
|
641
|
+
/**
|
|
642
|
+
* A ORGANIZAÇÃO SÓ CONTA COMPONENTE - `SHOWS_A_COMPONENT`, a mesma exclusão de baixo.
|
|
643
|
+
*
|
|
644
|
+
* Este bloco roda onze linhas ANTES do filtro, e por isso contava story e teste como código
|
|
645
|
+
* do produto: 75 arquivos onde o cliente tem 37 (`codelevel-monorepo/packages/ui`, 30/08). O
|
|
646
|
+
* número que a organização declara é o que ele confere na mão, então ele conta o que ele
|
|
647
|
+
* conta quando confere.
|
|
648
|
+
*/
|
|
649
|
+
if (/\.(tsx|jsx|vue|svelte)$/i.test(rel) && !SHOWS_A_COMPONENT.test(rel)) {
|
|
625
650
|
const parts = rel.split("/");
|
|
626
651
|
// Every ancestor learns the name of the child directly under it, which is all
|
|
627
652
|
// the shape detector needs and is one pass rather than a tree walk.
|
|
628
653
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
629
654
|
const at = parts.slice(0, i).join("/");
|
|
630
|
-
const entry = dirs.get(at) ??
|
|
655
|
+
const entry = dirs.get(at) ?? freshDir();
|
|
631
656
|
entry.dirs.add(parts[i]);
|
|
632
657
|
entry.files += 1;
|
|
633
658
|
dirs.set(at, entry);
|
|
634
659
|
}
|
|
660
|
+
/**
|
|
661
|
+
* A PASTA-MÃE, CREDITADA PELO ARQUIVO QUE ELA SEGURA - ver `DirEntry.direct`.
|
|
662
|
+
*
|
|
663
|
+
* O laço acima nunca roda para um arquivo na RAIZ do escopo (`parts.length - 1 === 0`), e
|
|
664
|
+
* era isso que fazia um projeto de quatro componentes soltos - uma forma real de biblioteca
|
|
665
|
+
* pequena - não produzir entrada nenhuma e ficar em silêncio absoluto sobre a própria
|
|
666
|
+
* organização. Aqui a raiz é uma pasta como as outras.
|
|
667
|
+
*/
|
|
668
|
+
const home = parts.slice(0, -1).join("/");
|
|
669
|
+
const entry = dirs.get(home) ?? freshDir();
|
|
670
|
+
entry.direct += 1;
|
|
671
|
+
dirs.set(home, entry);
|
|
635
672
|
}
|
|
636
673
|
sources.push({ file: rel, source: src });
|
|
637
674
|
readSignalsInto(signals, importTally, rel, src);
|
|
@@ -639,7 +676,7 @@ export async function takeCensus(root, opts) {
|
|
|
639
676
|
// Stories and tests compose components to SHOW them; counting those as the
|
|
640
677
|
// product's own composition is the same lie the colour census told before
|
|
641
678
|
// they were set aside.
|
|
642
|
-
if (
|
|
679
|
+
if (!SHOWS_A_COMPONENT.test(rel)) {
|
|
643
680
|
scanComponentsInto(tally, rel, src, internal);
|
|
644
681
|
/**
|
|
645
682
|
* The other half: what this file EXPORTS, with the axes its types declare. A
|
|
@@ -1282,11 +1319,24 @@ export async function takeCensus(root, opts) {
|
|
|
1282
1319
|
}
|
|
1283
1320
|
}
|
|
1284
1321
|
progress.done();
|
|
1285
|
-
|
|
1322
|
+
/** A ÁRVORE MEDIDA, uma vez - as três leituras abaixo saem todas dela. */
|
|
1323
|
+
const dirEntries = [...dirs.entries()].map(([path, v]) => ({
|
|
1286
1324
|
path: path || ".",
|
|
1287
1325
|
dirs: [...v.dirs],
|
|
1288
1326
|
files: v.files,
|
|
1289
|
-
|
|
1327
|
+
direct: v.direct,
|
|
1328
|
+
}));
|
|
1329
|
+
const architectures = detectArchitectures(dirEntries);
|
|
1330
|
+
/**
|
|
1331
|
+
* A LACUNA, quando nada foi reconhecido - ver `architectureGap` (`INV-COB-08`). Nenhuma
|
|
1332
|
+
* decisão aqui: este arquivo IMPRIME e GRAVA o que aquela camada decidiu.
|
|
1333
|
+
*/
|
|
1334
|
+
const gap = architectureGap(dirEntries, architectures);
|
|
1335
|
+
/**
|
|
1336
|
+
* ONDE O PRÓXIMO COMPONENTE VAI - derivado do escopo que ele apontou, e por isso a
|
|
1337
|
+
* pergunta sobrevive a não reconhecermos organização nenhuma.
|
|
1338
|
+
*/
|
|
1339
|
+
const newHome = proposeNewHome(dirEntries, scopeLabel);
|
|
1290
1340
|
const d = diagnose(reports);
|
|
1291
1341
|
const allComposed = tallyToInventory(tally, 400);
|
|
1292
1342
|
/**
|
|
@@ -1419,7 +1469,7 @@ export async function takeCensus(root, opts) {
|
|
|
1419
1469
|
}
|
|
1420
1470
|
edges.set(file, edgesIn(src));
|
|
1421
1471
|
const rel = join(u.label, relative(u.path, file));
|
|
1422
|
-
if (
|
|
1472
|
+
if (SHOWS_A_COMPONENT.test(rel)) {
|
|
1423
1473
|
continue;
|
|
1424
1474
|
}
|
|
1425
1475
|
usageSeen.add(rel);
|
|
@@ -2007,6 +2057,16 @@ export async function takeCensus(root, opts) {
|
|
|
2007
2057
|
say(body(paint.faint(` ${describeArchitecture(a)}`)));
|
|
2008
2058
|
}
|
|
2009
2059
|
}
|
|
2060
|
+
else if (gap) {
|
|
2061
|
+
/**
|
|
2062
|
+
* A MESMA SEÇÃO, NO MESMO LUGAR DA SAÍDA - `INV-COB-08`. Uma leitura que rodou e não
|
|
2063
|
+
* reconheceu nada tinha esta seção inteira desaparecendo, e a ausência lê como "não
|
|
2064
|
+
* medimos nada aqui" em vez de "medimos, e a régua é nossa".
|
|
2065
|
+
*/
|
|
2066
|
+
say("");
|
|
2067
|
+
say(section("How this project is organised"));
|
|
2068
|
+
say(body(describeGap(gap, scopeLabel)));
|
|
2069
|
+
}
|
|
2010
2070
|
if (skips.length > 0) {
|
|
2011
2071
|
say("");
|
|
2012
2072
|
say(body(describeGate(scoped.length, skips.length)));
|
|
@@ -2492,13 +2552,24 @@ export async function takeCensus(root, opts) {
|
|
|
2492
2552
|
line: homeLine(a),
|
|
2493
2553
|
rule: architectureRule(a, scopeLabel),
|
|
2494
2554
|
})),
|
|
2495
|
-
...(proposeNewHome(architectures, scopeLabel)
|
|
2496
|
-
? {
|
|
2497
|
-
newComponentHome: proposeNewHome(architectures, scopeLabel),
|
|
2498
|
-
}
|
|
2499
|
-
: {}),
|
|
2500
2555
|
}
|
|
2501
2556
|
: {}),
|
|
2557
|
+
/**
|
|
2558
|
+
* A LEITURA QUE RODOU E NÃO RECONHECEU NADA - `INV-COB-08`, ver `architectureGap`.
|
|
2559
|
+
*
|
|
2560
|
+
* Só os DOIS números medidos. Sem nome de forma, sem regra e sem caminho: nada foi
|
|
2561
|
+
* reconhecido, então qualquer um dos três seria adivinhação nossa gravada como fato dele.
|
|
2562
|
+
*/
|
|
2563
|
+
...(gap ? { architectureGap: gap } : {}),
|
|
2564
|
+
/**
|
|
2565
|
+
* FORA DO `if` DAS FORMAS, e é isso que mantém a pergunta viva - `INV-COB-08`.
|
|
2566
|
+
*
|
|
2567
|
+
* "Daqui em diante vai num lugar novo" é derivada do escopo que ele apontou e não depende
|
|
2568
|
+
* de reconhecermos organização nenhuma. Presa dentro do bloco acima, ela sumia junto com o
|
|
2569
|
+
* resto justamente para quem mais precisa dela: o projeto sobre o qual não temos nada a
|
|
2570
|
+
* dizer é o único cujo agente não recebe regra de onde criar componente.
|
|
2571
|
+
*/
|
|
2572
|
+
...(newHome ? { newComponentHome: newHome } : {}),
|
|
2502
2573
|
...(skips.length > 0
|
|
2503
2574
|
? {
|
|
2504
2575
|
/**
|
|
@@ -52,12 +52,98 @@ const SHAPES = [
|
|
|
52
52
|
describe: (a) => `Every component sits directly under \`${a.root}\`, one folder deep. It is the shape that stays legible longest while a system is small, and the one to revisit when the folder passes about forty names.`,
|
|
53
53
|
},
|
|
54
54
|
];
|
|
55
|
+
/**
|
|
56
|
+
* A MESMA ORGANIZAÇÃO VISTA DE MAIS PERTO NÃO É UMA SEGUNDA ESCOLHA (`INV-COB-09`).
|
|
57
|
+
*
|
|
58
|
+
* O QUE O CLIENTE GANHA: a pergunta "qual organização tem PRIORIDADE" só lhe oferece
|
|
59
|
+
* organizações que disputam de verdade. Sem isto ele escolhe entre uma escada atômica e uma
|
|
60
|
+
* pasta que mora DENTRO dela - a mesma organização, medida de duas distâncias -, e qualquer
|
|
61
|
+
* resposta que ele der é a mesma resposta. Uma pergunta cujas opções não se excluem não é uma
|
|
62
|
+
* pergunta: é um formulário que ele preenche sem que nada mude no que os agentes dele escrevem.
|
|
63
|
+
*
|
|
64
|
+
* A PROPRIEDADE: uma organização é reconhecida no lugar mais RASO que a explica. Uma forma é a
|
|
65
|
+
* mesma organização vista de mais perto quando é do MESMO tipo de uma que já a cobre, ou quando é
|
|
66
|
+
* a leitura genérica (`flat`) de um lugar que outra forma já nomeia com mais informação. Formas de
|
|
67
|
+
* tipos DIFERENTES descrevem fatos diferentes, e nenhuma apaga a outra - o peso (`MINOR_SHARE`) já
|
|
68
|
+
* decide qual delas pede decisão e qual é dita como canto.
|
|
69
|
+
*
|
|
70
|
+
* O DESEMPATE NO MESMO ROOT continua sendo só o de `flat`, e ele não muda aqui: duas formas
|
|
71
|
+
* específicas na mesma pasta (`layered` e `colocated` em `src`, por exemplo) são duas leituras
|
|
72
|
+
* legítimas do mesmo lugar, e escolher entre elas é justamente a decisão que a pergunta existe
|
|
73
|
+
* para pedir. `flat` é a forma mais permissiva das cinco - basta uma pasta `components` -, então
|
|
74
|
+
* onde uma específica também casa, ela é a mesma coisa dita com menos informação.
|
|
75
|
+
*
|
|
76
|
+
* MEDIÇÃO de 31/08 - população e régua de enumeração de escopo escritas UMA vez em
|
|
77
|
+
* `contracts/camada-4-cobertura.md` (`INV-COB-09`), e não recopiadas aqui: 65 formas reconhecidas
|
|
78
|
+
* em 43 escopos passam a 56; as 9 subordinadas dizem o mesmo fato de um ancestral, e estão em 6
|
|
79
|
+
* escopos. Foto daquelas populações naquele dia, nunca invariante.
|
|
80
|
+
*/
|
|
81
|
+
export function dropShapesAlreadyExplained(found) {
|
|
82
|
+
return found.filter((a) => !found.some((b) => b !== a && explains(b, a)));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* `other` é a MESMA organização que `shape`, vista de mais longe?
|
|
86
|
+
*
|
|
87
|
+
* A pergunta não é "está por perto": é se as duas dizem o mesmo fato. Uma forma é a mesma
|
|
88
|
+
* organização vista de mais perto quando é do MESMO tipo de uma que já a cobre, ou quando é a
|
|
89
|
+
* leitura genérica (`flat`) de um lugar que outra forma já nomeia com mais informação.
|
|
90
|
+
*
|
|
91
|
+
* FORMAS DE TIPOS DIFERENTES DESCREVEM FATOS DIFERENTES, e nenhuma apaga a outra - nem quando uma
|
|
92
|
+
* mora dentro da outra. O layout React mais comum que existe é a prova: `src/{components}` com
|
|
93
|
+
* `src/components/{atoms, molecules, organisms}` embaixo. Com o ancestral vencendo sempre, a
|
|
94
|
+
* escada atômica SOME, e o que sobra é a leitura mais pobre das duas - o cliente recebe no
|
|
95
|
+
* `CLAUDE.md` dele a regra "Every component sits directly under `src`, one folder deep", que é
|
|
96
|
+
* FALSA no repositório dele, e o caminho `src/{components}`, que manda o agente parar um degrau
|
|
97
|
+
* antes de onde o componente nasce. Uma opção errada é pior que duas opções: ele nem sabe que
|
|
98
|
+
* havia o que decidir.
|
|
99
|
+
*
|
|
100
|
+
* Quem decide qual das duas pede decisão e qual é dita como canto é o PESO (`MINOR_SHARE`), e não
|
|
101
|
+
* este predicado - uma escada de 6 arquivos dentro de um `colocated` de 780 continua sendo dita,
|
|
102
|
+
* e continua não virando pergunta.
|
|
103
|
+
*/
|
|
104
|
+
function explains(other, shape) {
|
|
105
|
+
if (isUnderPath(other.root, shape.root))
|
|
106
|
+
return other.kind === shape.kind || shape.kind === "flat";
|
|
107
|
+
return (pathKey(other.root) === pathKey(shape.root) &&
|
|
108
|
+
shape.kind === "flat" &&
|
|
109
|
+
other.kind !== "flat");
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* O CAMINHO COMPARÁVEL - a raiz do escopo é escrita `.` pela caminhada, e comparada com `""` ela é
|
|
113
|
+
* a mesma pasta. Comparar as strings cruas faria a raiz deixar de explicar o que está embaixo
|
|
114
|
+
* dela, que é o caso mais comum de todos.
|
|
115
|
+
*
|
|
116
|
+
* AS DUAS OUTRAS NORMALIZAÇÕES SÃO DEFENSIVAS, e ficam nomeadas como tal: barra final e `./` na
|
|
117
|
+
* frente não são alcançáveis pela caminhada real - `import.ts` escreve `path || "."` e monta o
|
|
118
|
+
* resto com `parts.slice(…).join("/")` -, então nenhum teste pode reprová-las por comportamento.
|
|
119
|
+
* Elas custam duas chamadas de `replace` e protegem quem montar a árvore à mão.
|
|
120
|
+
*/
|
|
121
|
+
function pathKey(path) {
|
|
122
|
+
const tidy = path.replace(/\/+$/, "").replace(/^\.\//, "");
|
|
123
|
+
return tidy === "." ? "" : tidy;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* `path` está DENTRO de `ancestor` - por SEGMENTO, nunca por prefixo de texto.
|
|
127
|
+
*
|
|
128
|
+
* `"src/ui-kit".startsWith("src/ui")` é verdadeiro e está errado: são duas pastas irmãs, e por
|
|
129
|
+
* essa comparação uma organização real desapareceria da pergunta sem que nada a explicasse.
|
|
130
|
+
*/
|
|
131
|
+
function isUnderPath(ancestor, path) {
|
|
132
|
+
const a = pathKey(ancestor);
|
|
133
|
+
const p = pathKey(path);
|
|
134
|
+
/**
|
|
135
|
+
* A MESMA PASTA JÁ CAI FORA POR CONSTRUÇÃO, e por isso não há guarda para ela: com `a === p`,
|
|
136
|
+
* `p.startsWith(`${a}/`)` é falso (uma pasta não é filha de si mesma) e a raiz cai no `p !== ""`.
|
|
137
|
+
* Uma guarda a mais aqui seria linha que nenhum caso alcança - e código que nenhum caso alcança
|
|
138
|
+
* é código que nenhum teste pode reprovar.
|
|
139
|
+
*/
|
|
140
|
+
return a === "" ? p !== "" : p.startsWith(`${a}/`);
|
|
141
|
+
}
|
|
55
142
|
/**
|
|
56
143
|
* Which shapes this tree actually has, ranked by how much code each holds.
|
|
57
144
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* architecture, it is the same one seen from further away.
|
|
145
|
+
* O que sobra é o que governa código que nenhuma outra forma já governa - a subordinação inteira
|
|
146
|
+
* mora em `dropShapesAlreadyExplained` (`INV-COB-09`), numa redação só.
|
|
61
147
|
*/
|
|
62
148
|
export function detectArchitectures(entries) {
|
|
63
149
|
const found = [];
|
|
@@ -76,10 +162,6 @@ export function detectArchitectures(entries) {
|
|
|
76
162
|
}
|
|
77
163
|
if (!complete)
|
|
78
164
|
continue;
|
|
79
|
-
if (shape.kind === "flat" &&
|
|
80
|
-
found.some((f) => f.root === entry.path && f.kind !== "flat")) {
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
165
|
found.push({
|
|
84
166
|
kind: shape.kind,
|
|
85
167
|
root: entry.path,
|
|
@@ -88,10 +170,7 @@ export function detectArchitectures(entries) {
|
|
|
88
170
|
});
|
|
89
171
|
}
|
|
90
172
|
}
|
|
91
|
-
return found
|
|
92
|
-
.filter((a) => a.kind !== "flat" ||
|
|
93
|
-
!found.some((b) => b.root === a.root && b.kind !== "flat"))
|
|
94
|
-
.sort((a, b) => b.files - a.files || a.root.localeCompare(b.root));
|
|
173
|
+
return dropShapesAlreadyExplained(found).sort((a, b) => b.files - a.files || a.root.localeCompare(b.root));
|
|
95
174
|
}
|
|
96
175
|
/** The sentence that becomes the rule. Written for an agent about to add a file. */
|
|
97
176
|
export function describeArchitecture(a) {
|
|
@@ -165,13 +244,26 @@ export function homeLine(a) {
|
|
|
165
244
|
* ela a pergunta é sobre o passado.
|
|
166
245
|
*
|
|
167
246
|
* O caminho é DERIVADO, não inventado: fica dentro do escopo que a pessoa já apontou como
|
|
168
|
-
* fonte de verdade, e usa `src/` só quando
|
|
247
|
+
* fonte de verdade, e usa `src/` só quando a árvore medida mostra que o escopo tem `src`.
|
|
169
248
|
* Sem nome de sistema dentro dele de propósito - quem quer isso digita, e digitar um nome
|
|
170
249
|
* é mais honesto do que a gente adivinhar a grafia dele.
|
|
171
250
|
*/
|
|
172
|
-
export function proposeNewHome(
|
|
251
|
+
export function proposeNewHome(
|
|
252
|
+
/**
|
|
253
|
+
* A ÁRVORE MEDIDA, e não a lista do que foi reconhecido - a porta fechada.
|
|
254
|
+
*
|
|
255
|
+
* `hasSrc` era lido da lista de formas, e com ela VAZIA - que é exatamente o caso em que
|
|
256
|
+
* nenhuma organização conhecida apareceu - a resposta era sempre "não tem `src`": num
|
|
257
|
+
* projeto cujos 75 arquivos moram todos sob `src/`, a proposta mandava o cliente para uma
|
|
258
|
+
* pasta na raiz. Duas entradas (a árvore e as formas) podem discordar; uma não pode, e as
|
|
259
|
+
* formas saem da árvore aqui dentro.
|
|
260
|
+
*/
|
|
261
|
+
entries, scope) {
|
|
262
|
+
const found = detectArchitectures(entries);
|
|
173
263
|
const base = scope ? scope.replace(/\/+$/, "") : "";
|
|
174
|
-
const hasSrc =
|
|
264
|
+
const hasSrc = entries.some((e) => e.path === "src" ||
|
|
265
|
+
e.path.startsWith("src/") ||
|
|
266
|
+
(isRoot(e.path) && e.dirs.includes("src")));
|
|
175
267
|
const trunk = `${base ? `${base}/` : ""}${hasSrc ? "src/components" : "components"}`;
|
|
176
268
|
const fresh = `${trunk}/{atoms, molecules, organisms}`;
|
|
177
269
|
/** Se a proposta é o que já existe, ela não é uma pasta nova - e oferecê-la seria repetir a opção 1. */
|
|
@@ -233,3 +325,90 @@ export function describeChoice(found) {
|
|
|
233
325
|
const list = options.map(say).join(", ");
|
|
234
326
|
return `${options.length} shapes govern real code, and none of them is a mistake: ${list}.${notes.length > 0 ? ` Smaller corners, reported and not asked about: ${notes.map(say).join(", ")}.` : ""} Pick which one has PRIORITY - it becomes the rule every prompt reads, and the others stay true without deciding.`;
|
|
235
327
|
}
|
|
328
|
+
/** A raiz do escopo, que a caminhada escreve como `.` e o mapa guarda como "". */
|
|
329
|
+
function isRoot(path) {
|
|
330
|
+
return path === "." || path === "";
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* A ORGANIZAÇÃO QUE NINGUÉM SOUBE NOMEAR - declarada, nunca calada (`INV-COB-08`).
|
|
334
|
+
*
|
|
335
|
+
* O QUE O CLIENTE GANHA: quando a leitura roda e nenhuma das organizações conhecidas
|
|
336
|
+
* aparece, ele continua recebendo uma resposta - quantos arquivos de componente foram
|
|
337
|
+
* medidos, e em quantas pastas eles estão - com a frase dizendo que o limite é a nossa
|
|
338
|
+
* régua e não o projeto dele. Sem isto a seção inteira sobre organização SOME da saída, e
|
|
339
|
+
* silêncio é indistinguível de "não medimos nada aqui": ele fica sem saber que a régua
|
|
340
|
+
* rodou, sem o número que ela mediu, e sem a chance de dizer onde o próximo componente vai.
|
|
341
|
+
*
|
|
342
|
+
* MEDIÇÃO de 30/08 - a população e a régua de enumeração de escopo estão escritas UMA vez, em
|
|
343
|
+
* `contracts/camada-4-cobertura.md` (`INV-COB-08`), e não são recopiadas aqui: 6 de 43 escopos
|
|
344
|
+
* com componentes (14%) não casam nenhuma organização conhecida, e o maior deles tem 37
|
|
345
|
+
* arquivos. No banco de produção, na mesma data, há 1 sistema com censo de import guardado, e
|
|
346
|
+
* ele é um dos mudos - 1 de 1. Foto daquelas populações naquele dia, nunca invariante.
|
|
347
|
+
*
|
|
348
|
+
* O QUE ESTE BLOCO NÃO FAZ, de propósito: reconhecer uma organização nova. Uma sexta forma é
|
|
349
|
+
* outra entrega. Aqui a ausência de reconhecimento vira DECLARAÇÃO, nunca um palpite.
|
|
350
|
+
*/
|
|
351
|
+
/**
|
|
352
|
+
* ABAIXO DISTO NÃO HÁ ORGANIZAÇÃO A DECLARAR - há conteúdo.
|
|
353
|
+
*
|
|
354
|
+
* MEDIÇÃO de 30/08 - população e régua de enumeração de escopo em
|
|
355
|
+
* `contracts/camada-4-cobertura.md` (`INV-COB-08`): dos 6 escopos mudos, 4 têm um ou dois
|
|
356
|
+
* arquivos de componente ao todo. Um ou dois arquivos não organizam nada, e dizer "não sei
|
|
357
|
+
* nomear a sua organização" sobre eles é ruído com cara de lacuna. Os 2 que sobram são os que a
|
|
358
|
+
* frase existe para alcançar - 37 arquivos em 37 pastas, e 7 arquivos em 1 pasta. O 3 é lido
|
|
359
|
+
* dessa foto, e não é regra do produto.
|
|
360
|
+
*
|
|
361
|
+
* O piso é ancorado por LITERAIS no spec (2 cala, 3 fala) e não por esta constante: um teste
|
|
362
|
+
* escrito como `GAP_MIN_FILES - 1` acompanha a constante para onde ela for, e com ela em zero o
|
|
363
|
+
* produto imprimiria "We read 0 component files in 0 folders" - o zero pelado da lei 14 - com a
|
|
364
|
+
* suíte inteira verde.
|
|
365
|
+
*/
|
|
366
|
+
export const GAP_MIN_FILES = 3;
|
|
367
|
+
/**
|
|
368
|
+
* A LEITURA QUE RODOU E NÃO RECONHECEU NADA - o estado `unknown` desta leitura.
|
|
369
|
+
*
|
|
370
|
+
* Devolve `null` quando há forma reconhecida (aí quem fala é `describeChoice`) e quando o
|
|
371
|
+
* escopo é pequeno demais para ter organização (`GAP_MIN_FILES`).
|
|
372
|
+
*/
|
|
373
|
+
export function architectureGap(entries, found) {
|
|
374
|
+
if (found.length > 0)
|
|
375
|
+
return null;
|
|
376
|
+
/**
|
|
377
|
+
* SOMAR `direct`, e nunca `files`: a caminhada credita cada arquivo a TODOS os ancestrais,
|
|
378
|
+
* então somar `files` conta o mesmo arquivo uma vez por nível. `direct` é o arquivo na pasta
|
|
379
|
+
* onde ele está, uma vez só - a mesma conta que o `find` acima faz na máquina dele.
|
|
380
|
+
*/
|
|
381
|
+
let files = 0;
|
|
382
|
+
let folders = 0;
|
|
383
|
+
for (const e of entries) {
|
|
384
|
+
if (e.direct <= 0)
|
|
385
|
+
continue;
|
|
386
|
+
files += e.direct;
|
|
387
|
+
folders += 1;
|
|
388
|
+
}
|
|
389
|
+
if (files < GAP_MIN_FILES)
|
|
390
|
+
return null;
|
|
391
|
+
return { files, folders };
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* A FRASE, no tom das outras desta camada: o número medido, o motivo, e nenhuma acusação.
|
|
395
|
+
*
|
|
396
|
+
* A ordem é a da dúvida de quem lê - o que medimos, de quem é o limite, e o que isso custa
|
|
397
|
+
* a ele hoje. A segunda parte não é gentileza: sem ela a frase lê como um defeito do
|
|
398
|
+
* repositório dele, e o defeito é da régua.
|
|
399
|
+
*/
|
|
400
|
+
export function describeGap(g,
|
|
401
|
+
/**
|
|
402
|
+
* ONDE MEDIMOS, quando a leitura foi apontada para uma parte do repositório.
|
|
403
|
+
*
|
|
404
|
+
* "here" não resolve para nada na cabeça de quem rodou `--scope packages/ui` da raiz de um
|
|
405
|
+
* monorepo, e num import de dois lugares a mesma seção sai duas vezes falando de pastas
|
|
406
|
+
* diferentes. Sem rótulo a redação continua a mesma: uma leitura da raiz não tem outro
|
|
407
|
+
* lugar a que se referir.
|
|
408
|
+
*/
|
|
409
|
+
where) {
|
|
410
|
+
const files = `${g.files} component file${g.files === 1 ? "" : "s"}`;
|
|
411
|
+
const folders = `${g.folders} folder${g.folders === 1 ? "" : "s"}`;
|
|
412
|
+
const at = where ? `under \`${where}\`` : "here";
|
|
413
|
+
return `We read ${files} in ${folders} ${at}, and none of the ways of organising code this version can name fits them. That does not mean your project has no structure; it means ours does not have a name for yours yet. Nothing about where code lives was written into your rules, so an agent reading your \`CLAUDE.md\` guesses where a new component goes until you tell it.`;
|
|
414
|
+
}
|
package/dist/install-marks.js
CHANGED
|
@@ -375,7 +375,47 @@ export const CHECKER_SINCE = "0.16.308";
|
|
|
375
375
|
* nunca era alcançado. Medido: 1134 componentes, 77 mudaram, e a receita que o cliente recebe muda
|
|
376
376
|
* com eles. Uma medição anterior não produz o conserto, então a marca sobe.
|
|
377
377
|
*/
|
|
378
|
-
|
|
378
|
+
/**
|
|
379
|
+
* 0.16.336 -> 0.16.337 em 30/08 (INV-COB-08): a contagem de arquivos por organização deixou de
|
|
380
|
+
* incluir story, spec e teste. Um censo já gravado carrega o número inflado - `atomic@src/lib/SignalUI`
|
|
381
|
+
* dizia 69 arquivos onde há 38 - e esse número viaja para o CLAUDE.md do cliente como a EVIDÊNCIA da
|
|
382
|
+
* regra (`architectureRule().evidence`, "holding N component files"). Não há como corrigi-lo a partir
|
|
383
|
+
* do censo: saber quais daqueles arquivos eram story exige reler o disco, e só a remedição lê. Por
|
|
384
|
+
* isso a marca sobe.
|
|
385
|
+
*
|
|
386
|
+
* MEDIÇÃO de 30/08 - população e régua de enumeração de escopo em
|
|
387
|
+
* `contracts/camada-4-cobertura.md` (`INV-COB-08`): `frontend-hub/packages/ui` sai de 69 para 38
|
|
388
|
+
* (mesma forma, mesmo lugar); `codelevel/packages/ui` sai de 75 para 37. O ranking não mudou em
|
|
389
|
+
* nenhum dos 43 escopos medidos, então ninguém recebe uma organização DIFERENTE - só o número
|
|
390
|
+
* honesto.
|
|
391
|
+
*/
|
|
392
|
+
/**
|
|
393
|
+
* 0.16.337 -> 0.16.338 em 31/08 (`INV-COB-09`): uma organização passou a ser reconhecida no lugar
|
|
394
|
+
* mais RASO que a explica. Uma forma do MESMO tipo de outra que já a cobre, ou a leitura genérica
|
|
395
|
+
* de um lugar que outra já nomeia com mais informação, deixa de ser gravada como uma segunda
|
|
396
|
+
* organização. Tipos DIFERENTES continuam os dois gravados: eles dizem fatos diferentes sobre o
|
|
397
|
+
* mesmo código, e apagar o de dentro entregaria a leitura mais pobre como se fosse a única.
|
|
398
|
+
*
|
|
399
|
+
* A marca sobe porque um censo já gravado carrega as entradas duplicadas e não há como saber quais
|
|
400
|
+
* eram subordinadas sem reler a árvore do disco: a subordinação se decide comparando os CAMINHOS
|
|
401
|
+
* medidos, e um censo antigo guarda o que foi reconhecido, não a caminhada que o reconheceu. Só a
|
|
402
|
+
* remedição lê o disco.
|
|
403
|
+
*
|
|
404
|
+
* O QUE O `sync` ENTREGA, e o que ele NÃO entrega. Ele remede com `takeCensus`, então o campo
|
|
405
|
+
* `architectures` do censo é reescrito sem as entradas subordinadas e o censo reenviado já chega
|
|
406
|
+
* limpo. Ele NÃO reimprime a seção de organização no terminal - essa seção é do relatório de
|
|
407
|
+
* `import` -, e NÃO reescreve a regra no `CLAUDE.md` dele: aquela linha nasce da conversa de
|
|
408
|
+
* import, que lê `architectures[].rule` do censo, e `rulesFromCensus` não olha para este campo.
|
|
409
|
+
* Quem já tem a regra escrita a partir de uma organização subordinada continua com ela até um
|
|
410
|
+
* import novo, e dizer o contrário seria prometer que um comando conserta o que ele não conserta.
|
|
411
|
+
*
|
|
412
|
+
* MEDIÇÃO de 31/08 - população e régua de enumeração de escopo em
|
|
413
|
+
* `contracts/camada-4-cobertura.md` (`INV-COB-09`): 65 formas reconhecidas em 43 escopos passam a
|
|
414
|
+
* 56; as 9 subordinadas dizem o mesmo fato de um ancestral, e estão em 6 escopos. O hash do corpus
|
|
415
|
+
* NÃO se moveu, e isso é a informação: nenhum dos 28 apps dourados tem forma repetindo o que uma
|
|
416
|
+
* ancestral já diz, então a estabilidade ali prova ausência de regressão e não ausência de efeito.
|
|
417
|
+
*/
|
|
418
|
+
export const READER_SINCE = "0.16.338";
|
|
379
419
|
/**
|
|
380
420
|
* O QUE ESTÁ INSTALADO AQUI FICOU PARA TRÁS - e as DUAS condições que fazem isso ser verdade.
|
|
381
421
|
*
|
package/dist/merge-census.js
CHANGED
|
@@ -119,6 +119,32 @@ export function mergeCensus(list) {
|
|
|
119
119
|
let brokenRefs = first.brokenRefs ?? undefined;
|
|
120
120
|
let conventions = first.conventions ?? undefined;
|
|
121
121
|
let architectures = first.architectures ?? undefined;
|
|
122
|
+
/**
|
|
123
|
+
* A LACUNA DE ORGANIZAÇÃO, SOMADA - `INV-COB-08`, ver `architectureGap` em `import.ts`.
|
|
124
|
+
*
|
|
125
|
+
* A declaração de qualquer escopo SOBREVIVE à fusão. Ela diz "medimos aqui e não soubemos
|
|
126
|
+
* nomear"; deixá-la de fora porque o outro escopo foi reconhecido devolve o silêncio que a
|
|
127
|
+
* invariante existe para eliminar, um passo adiante - e o cliente que rodou com dois escopos
|
|
128
|
+
* é justamente quem tem mais lugar onde a resposta pode sumir.
|
|
129
|
+
*
|
|
130
|
+
* SOMADOS, e não o primeiro que aparece, porque os dois são CONTAGENS e contagem é aditiva:
|
|
131
|
+
* o total descreve exatamente as partes que ficaram sem nome, e nenhum arquivo contado aqui
|
|
132
|
+
* pertence a um escopo reconhecido. É o oposto do caso da cobertura logo abaixo, onde somar
|
|
133
|
+
* percentuais produziria um número que não descreve medição nenhuma.
|
|
134
|
+
*/
|
|
135
|
+
let gapFiles = first.architectureGap
|
|
136
|
+
?.files;
|
|
137
|
+
let gapFolders = first.architectureGap
|
|
138
|
+
?.folders;
|
|
139
|
+
/**
|
|
140
|
+
* ONDE O PRÓXIMO COMPONENTE VAI - o PRIMEIRO escopo vence, a mesma regra de `scope`.
|
|
141
|
+
*
|
|
142
|
+
* É um caminho, não uma contagem: dois caminhos não somam, e escolher o do escopo seguinte
|
|
143
|
+
* mandaria o agente para um lugar fora do escopo que o censo declara como o dele.
|
|
144
|
+
*/
|
|
145
|
+
const newComponentHome = list
|
|
146
|
+
.map((c) => c.newComponentHome)
|
|
147
|
+
.find((h) => typeof h === "string" && h.length > 0);
|
|
122
148
|
let components = first.components ?? undefined;
|
|
123
149
|
let defined = first.defined ?? undefined;
|
|
124
150
|
const stack = [...(first.project.stack ?? [])];
|
|
@@ -177,6 +203,11 @@ export function mergeCensus(list) {
|
|
|
177
203
|
brokenRefs = concat(brokenRefs, c.brokenRefs);
|
|
178
204
|
conventions = concat(conventions, c.conventions);
|
|
179
205
|
architectures = concat(architectures, c.architectures);
|
|
206
|
+
const gap = c.architectureGap;
|
|
207
|
+
if (gap) {
|
|
208
|
+
gapFiles = (gapFiles ?? 0) + (gap.files ?? 0);
|
|
209
|
+
gapFolders = (gapFolders ?? 0) + (gap.folders ?? 0);
|
|
210
|
+
}
|
|
180
211
|
components = concat(components, c.components);
|
|
181
212
|
defined = concat(defined, c.defined);
|
|
182
213
|
for (const s of c.project.stack ?? [])
|
|
@@ -260,6 +291,10 @@ export function mergeCensus(list) {
|
|
|
260
291
|
out.conventions = conventions;
|
|
261
292
|
if (architectures && architectures.length > 0)
|
|
262
293
|
out.architectures = architectures;
|
|
294
|
+
if (gapFiles !== undefined && gapFolders !== undefined)
|
|
295
|
+
out.architectureGap = { files: gapFiles, folders: gapFolders };
|
|
296
|
+
if (newComponentHome)
|
|
297
|
+
out.newComponentHome = newComponentHome;
|
|
263
298
|
if (components && components.length > 0)
|
|
264
299
|
out.components = components;
|
|
265
300
|
if (defined && defined.length > 0)
|
package/package.json
CHANGED