synthesisui 0.16.338 → 0.16.342
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/align.js +8 -2
- package/dist/commands/import.js +72 -17
- package/dist/commands/sync.js +1 -1
- package/dist/doctor/architecture.js +204 -26
- package/dist/install-marks.js +70 -1
- package/dist/merge-census.js +70 -16
- package/package.json +1 -1
package/dist/commands/align.js
CHANGED
|
@@ -313,8 +313,14 @@ const REPAIR_SAID = {
|
|
|
313
313
|
spacing: "spacing values in your system were connected to the steps your own scale declares",
|
|
314
314
|
orphans: "recipes your measured scope no longer declares were removed",
|
|
315
315
|
roles: "colour roles were pointed at the ramps your repository declares",
|
|
316
|
-
|
|
317
|
-
|
|
316
|
+
/**
|
|
317
|
+
* `census` SAIU DESTAS DUAS - `INV-VOC-05`. As três irmãs acima já dizem a coisa pelo efeito no
|
|
318
|
+
* projeto dele ("the steps your own scale declares"), e estas duas diziam pelo nome da nossa
|
|
319
|
+
* peça. O texto do `align` entra no contexto do modelo no `SessionStart`, e o que o agente lê é
|
|
320
|
+
* o que ele repete para a pessoa.
|
|
321
|
+
*/
|
|
322
|
+
reground: "the foundation was rebuilt from what we measured in your repository",
|
|
323
|
+
interpretation: "what we measured in your repository was read again, with a newer interpretation",
|
|
318
324
|
};
|
|
319
325
|
export function repairSaid(body, lock) {
|
|
320
326
|
if (!lock.slug)
|
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 { architectureGap, architectureRule, componentHome, describeArchitecture, describeChoice, describeGap, detectArchitectures, homeLine, proposeNewHome, } from "../doctor/architecture.js";
|
|
8
|
+
import { architectureGap, architectureRule, componentHome, describeArchitecture, describeChoice, describeGap, detectArchitectures, homeLine, packagingOf, proposeNewHome, resolvesAs, } 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";
|
|
@@ -44,7 +44,7 @@ import { frontierKind, packageRoot } from "../frontier-kind.js";
|
|
|
44
44
|
import { keyframesInSheets } from "../global-keyframes.js";
|
|
45
45
|
import { globalClassesWorn } from "../global-wear.js";
|
|
46
46
|
import { withLibraryStructure } from "../library-structure.js";
|
|
47
|
-
import { mergeCensus } from "../merge-census.js";
|
|
47
|
+
import { architectureGaps, mergeCensus } from "../merge-census.js";
|
|
48
48
|
import { claimName } from "../name-claim.js";
|
|
49
49
|
import { mergeNamespacePairs } from "../namespace-pairs.js";
|
|
50
50
|
import { namingQueue } from "../naming-queue.js";
|
|
@@ -567,8 +567,14 @@ export async function takeCensus(root, opts) {
|
|
|
567
567
|
* naming how the project is organised costs no second pass. See `architecture.ts`.
|
|
568
568
|
*/
|
|
569
569
|
const dirs = new Map();
|
|
570
|
-
/** Uma entrada nova da árvore, com os
|
|
571
|
-
const freshDir = () => ({
|
|
570
|
+
/** Uma entrada nova da árvore, com os contadores em zero e nada observado. */
|
|
571
|
+
const freshDir = () => ({
|
|
572
|
+
dirs: new Set(),
|
|
573
|
+
files: 0,
|
|
574
|
+
direct: 0,
|
|
575
|
+
index: false,
|
|
576
|
+
names: [],
|
|
577
|
+
});
|
|
572
578
|
/** Parent → child → the files that put one inside the other. */
|
|
573
579
|
const nesting = new Map();
|
|
574
580
|
/**
|
|
@@ -668,6 +674,21 @@ export async function takeCensus(root, opts) {
|
|
|
668
674
|
const home = parts.slice(0, -1).join("/");
|
|
669
675
|
const entry = dirs.get(home) ?? freshDir();
|
|
670
676
|
entry.direct += 1;
|
|
677
|
+
entry.names.push((parts[parts.length - 1] ?? "").replace(/\.[a-z]+$/i, ""));
|
|
678
|
+
dirs.set(home, entry);
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* O BARRIL, REGISTRADO FORA DO FILTRO DE COMPONENTE - e é por isso que ele precisa de
|
|
682
|
+
* bloco próprio.
|
|
683
|
+
*
|
|
684
|
+
* `index.ts` não é um arquivo de componente pela régua acima, então o bloco anterior nunca
|
|
685
|
+
* o vê. Sem este registro, a pasta que o cliente importa como uma unidade
|
|
686
|
+
* (`from "./ActivityRing"`) é indistinguível de uma pasta qualquer com um arquivo dentro.
|
|
687
|
+
*/
|
|
688
|
+
if (/(^|\/)index\.(tsx|ts|jsx|js|vue|svelte)$/i.test(rel)) {
|
|
689
|
+
const home = rel.split("/").slice(0, -1).join("/");
|
|
690
|
+
const entry = dirs.get(home) ?? freshDir();
|
|
691
|
+
entry.index = true;
|
|
671
692
|
dirs.set(home, entry);
|
|
672
693
|
}
|
|
673
694
|
sources.push({ file: rel, source: src });
|
|
@@ -1325,18 +1346,20 @@ export async function takeCensus(root, opts) {
|
|
|
1325
1346
|
dirs: [...v.dirs],
|
|
1326
1347
|
files: v.files,
|
|
1327
1348
|
direct: v.direct,
|
|
1349
|
+
/** COMO ESTA PASTA RESOLVE - a decisão é de `doctor/architecture.ts`; aqui só se mede. */
|
|
1350
|
+
resolves: resolvesAs(path || ".", v.index, v.names),
|
|
1328
1351
|
}));
|
|
1329
1352
|
const architectures = detectArchitectures(dirEntries);
|
|
1330
1353
|
/**
|
|
1331
1354
|
* A LACUNA, quando nada foi reconhecido - ver `architectureGap` (`INV-COB-08`). Nenhuma
|
|
1332
1355
|
* decisão aqui: este arquivo IMPRIME e GRAVA o que aquela camada decidiu.
|
|
1333
1356
|
*/
|
|
1334
|
-
const gap = architectureGap(dirEntries, architectures);
|
|
1335
1357
|
/**
|
|
1336
1358
|
* ONDE O PRÓXIMO COMPONENTE VAI - derivado do escopo que ele apontou, e por isso a
|
|
1337
1359
|
* pergunta sobrevive a não reconhecermos organização nenhuma.
|
|
1338
1360
|
*/
|
|
1339
1361
|
const newHome = proposeNewHome(dirEntries, scopeLabel);
|
|
1362
|
+
const gap = architectureGap(dirEntries, architectures, scopeLabel ?? ".", newHome);
|
|
1340
1363
|
const d = diagnose(reports);
|
|
1341
1364
|
const allComposed = tallyToInventory(tally, 400);
|
|
1342
1365
|
/**
|
|
@@ -2054,7 +2077,12 @@ export async function takeCensus(root, opts) {
|
|
|
2054
2077
|
if (choice)
|
|
2055
2078
|
say(body(choice));
|
|
2056
2079
|
for (const a of architectures.slice(0, 4)) {
|
|
2057
|
-
|
|
2080
|
+
/**
|
|
2081
|
+
* O ESCOPO VAI JUNTO, ou o terminal imprime um caminho que não existe na raiz dele.
|
|
2082
|
+
* `a.root` é relativo ao escopo medido - num monorepo lido com `--scope packages/ui`,
|
|
2083
|
+
* `src/lib/SignalUI` sozinho manda a pessoa procurar na pasta errada.
|
|
2084
|
+
*/
|
|
2085
|
+
say(body(paint.faint(` ${describeArchitecture(a, scopeLabel)}`)));
|
|
2058
2086
|
}
|
|
2059
2087
|
}
|
|
2060
2088
|
else if (gap) {
|
|
@@ -2065,7 +2093,7 @@ export async function takeCensus(root, opts) {
|
|
|
2065
2093
|
*/
|
|
2066
2094
|
say("");
|
|
2067
2095
|
say(section("How this project is organised"));
|
|
2068
|
-
say(body(describeGap(gap
|
|
2096
|
+
say(body(describeGap(gap)));
|
|
2069
2097
|
}
|
|
2070
2098
|
if (skips.length > 0) {
|
|
2071
2099
|
say("");
|
|
@@ -2545,22 +2573,35 @@ export async function takeCensus(root, opts) {
|
|
|
2545
2573
|
: {}),
|
|
2546
2574
|
...(architectures.length > 0
|
|
2547
2575
|
? {
|
|
2548
|
-
architectures: architectures.slice(0, 6).map((a) =>
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2576
|
+
architectures: architectures.slice(0, 6).map((a) => {
|
|
2577
|
+
/**
|
|
2578
|
+
* O EMPACOTAMENTO DAQUELE LUGAR, medido uma vez e usado nas duas pontas: ele entra
|
|
2579
|
+
* na regra que vai para o `CLAUDE.md` dele e viaja no censo como fato conferível.
|
|
2580
|
+
*/
|
|
2581
|
+
const packaging = packagingOf(dirEntries, a.root);
|
|
2582
|
+
return {
|
|
2583
|
+
...a,
|
|
2584
|
+
/** O caminho e a frase que a PERGUNTA usa verbatim - ver `componentHome`. */
|
|
2585
|
+
home: componentHome(a, scopeLabel),
|
|
2586
|
+
line: homeLine(a),
|
|
2587
|
+
rule: architectureRule(a, packaging, scopeLabel),
|
|
2588
|
+
...(packaging ? { packaging } : {}),
|
|
2589
|
+
};
|
|
2590
|
+
}),
|
|
2555
2591
|
}
|
|
2556
2592
|
: {}),
|
|
2557
2593
|
/**
|
|
2558
2594
|
* A LEITURA QUE RODOU E NÃO RECONHECEU NADA - `INV-COB-08`, ver `architectureGap`.
|
|
2559
2595
|
*
|
|
2560
|
-
*
|
|
2561
|
-
*
|
|
2596
|
+
* UMA LISTA DE UM, porque esta função mede UM lugar. O merge concatena as de cada escopo,
|
|
2597
|
+
* e é a lista que mantém cada número junto do lugar onde ele foi medido.
|
|
2598
|
+
*
|
|
2599
|
+
* Os dois números, o lugar, e a frase que os declara. Sem nome de forma, sem regra e sem
|
|
2600
|
+
* caminho de destino: nada foi reconhecido, então qualquer um dos três seria adivinhação
|
|
2601
|
+
* nossa gravada como fato dele. A frase é a exceção, e ela é declarada no tipo: fala da
|
|
2602
|
+
* NOSSA régua, e a única oração com data de validade fica no terminal.
|
|
2562
2603
|
*/
|
|
2563
|
-
...(gap ? {
|
|
2604
|
+
...(gap ? { architectureGaps: [gap] } : {}),
|
|
2564
2605
|
/**
|
|
2565
2606
|
* FORA DO `if` DAS FORMAS, e é isso que mantém a pergunta viva - `INV-COB-08`.
|
|
2566
2607
|
*
|
|
@@ -4063,6 +4104,20 @@ export async function runImport(opts) {
|
|
|
4063
4104
|
}
|
|
4064
4105
|
try {
|
|
4065
4106
|
census = JSON.parse(raw);
|
|
4107
|
+
/**
|
|
4108
|
+
* A LACUNA DE UM CENSO ANTIGO, LIDA AQUI - `INV-COB-08`.
|
|
4109
|
+
*
|
|
4110
|
+
* Este é o único caminho por onde um censo medido por outra versão entra: a fusão só vê o
|
|
4111
|
+
* que `takeCensus` acabou de produzir. Um arquivo de 0.16.337 ou 0.16.338 traz o par somado
|
|
4112
|
+
* `architectureGap`, que nenhuma superfície de hoje lê - sem esta linha, o agente que
|
|
4113
|
+
* anotou aquele censo e o reenvia perde a declaração de que a leitura rodou e não
|
|
4114
|
+
* reconheceu nada, que é exatamente o silêncio que a invariante existe para eliminar.
|
|
4115
|
+
*
|
|
4116
|
+
* O lugar não é inventado: a forma antiga não o guardou, e a entrada convertida diz isso.
|
|
4117
|
+
*/
|
|
4118
|
+
const carried = architectureGaps(census);
|
|
4119
|
+
if (carried.length > 0)
|
|
4120
|
+
census.architectureGaps = carried;
|
|
4066
4121
|
}
|
|
4067
4122
|
catch {
|
|
4068
4123
|
console.log(section("Import"));
|
package/dist/commands/sync.js
CHANGED
|
@@ -115,7 +115,7 @@ export async function sync(opts) {
|
|
|
115
115
|
*/
|
|
116
116
|
const measured = existsSync(join(root, "_synthesisui", "census.json"));
|
|
117
117
|
console.log(body(measured
|
|
118
|
-
? "This repo has
|
|
118
|
+
? "This repo has been measured but has no installed system, so there is nowhere to send it. `import` created the system on the platform; bringing it back is what tells sync which one this repo feeds:"
|
|
119
119
|
: "No installed system here, so there is nowhere to sync to. Install one first:"));
|
|
120
120
|
console.log(body(paint.blue(" npx synthesisui@latest add <slug>")));
|
|
121
121
|
console.log(body(paint.faint(" npx synthesisui@latest list --mine # the slugs you own")));
|
|
@@ -26,12 +26,12 @@ const SHAPES = [
|
|
|
26
26
|
["molecules", "molecule"],
|
|
27
27
|
["organisms", "organism"],
|
|
28
28
|
],
|
|
29
|
-
describe: (a) => `Components live in an atomic ladder under \`${
|
|
29
|
+
describe: (a, where) => `Components live in an atomic ladder under \`${where}\` - ${a.evidence.join("/")}. A new component goes on the rung that matches what it is made of: an atom composes nothing of yours, a molecule composes atoms, an organism is a whole region of a screen. Put it on the wrong rung and the ladder stops being true.`,
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
kind: "feature",
|
|
33
33
|
needs: [["features", "modules", "domains"]],
|
|
34
|
-
describe: (
|
|
34
|
+
describe: (_a, where) => `Code is organised BY FEATURE under \`${where}\`. A new component belongs to the feature that uses it, beside its own logic, and only moves up to the shared layer when a second feature needs it. Reaching across features is the thing this shape exists to prevent.`,
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
kind: "layered",
|
|
@@ -39,19 +39,116 @@ const SHAPES = [
|
|
|
39
39
|
["ui", "components"],
|
|
40
40
|
["hooks", "lib", "utils"],
|
|
41
41
|
],
|
|
42
|
-
describe: (a) => `Code is split by LAYER under \`${
|
|
42
|
+
describe: (a, where) => `Code is split by LAYER under \`${where}\` - ${a.evidence.join(", ")}. A component goes in the ui layer and nothing else does; state and helpers stay in theirs. A component reaching into another layer is fine, a helper reaching back into ui is not.`,
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
kind: "colocated",
|
|
46
46
|
needs: [["app", "routes", "pages"]],
|
|
47
|
-
describe: (
|
|
47
|
+
describe: (_a, where) => `Components sit BESIDE THE ROUTE that uses them, under \`${where}\`. A new one starts local to its page and only moves to the shared folder when a second page needs it - which keeps the shared folder meaning "shared" rather than "everything".`,
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
kind: "flat",
|
|
51
51
|
needs: [["components"]],
|
|
52
|
-
describe: (
|
|
52
|
+
describe: (_a, where) => `Every component sits directly under \`${where}\`, 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
|
+
* O MESMO NOME, MÓDULO CAIXA E SEPARADOR - a única normalização deste arquivo.
|
|
57
|
+
*
|
|
58
|
+
* Ela não inventa convenção: `Tooltip` e `tooltip`, `ContentWrapper` e `content-wrapper` são o
|
|
59
|
+
* mesmo nome escrito com a preferência de quem digitou. Comparar as strings cruas faria a
|
|
60
|
+
* régua responder por convenção de nome de arquivo, e duas pessoas com a MESMA organização
|
|
61
|
+
* receberiam respostas opostas.
|
|
62
|
+
*/
|
|
63
|
+
export function sameName(a, b) {
|
|
64
|
+
const tidy = (x) => x.toLowerCase().replace(/[-_.\s]/g, "");
|
|
65
|
+
return tidy(a) === tidy(b);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* COMO UMA PASTA RESOLVE - lido dos fatos que a caminhada mediu, num lugar só.
|
|
69
|
+
*
|
|
70
|
+
* O barril vence a eponímia quando os dois existem, porque é ele que decide o que o import
|
|
71
|
+
* escreve: numa pasta com `index.ts` e `Button.tsx`, quem importa escreve o nome da PASTA.
|
|
72
|
+
*/
|
|
73
|
+
export function resolvesAs(folder, hasIndex, componentNames) {
|
|
74
|
+
const leaf = folder.split("/").pop() ?? folder;
|
|
75
|
+
const eponymous = componentNames.some((n) => sameName(n, leaf));
|
|
76
|
+
/**
|
|
77
|
+
* UM BARRIL NÃO BASTA - e este era o falso positivo que uma revisão de QA pegou.
|
|
78
|
+
*
|
|
79
|
+
* `src/components/{Card,Pill,Tag}.tsx` com um `index.ts` ao lado tinha barril e virava
|
|
80
|
+
* "módulo de um componente". Num repositório onde NENHUM componente mora em pasta própria, a
|
|
81
|
+
* regra passava a mandar o agente criar `src/Toast/Toast.tsx` - o defeito do `frontend-hub`
|
|
82
|
+
* ao contrário, e uma pessoa move o arquivo do mesmo jeito.
|
|
83
|
+
*
|
|
84
|
+
* O que separa os dois não é o barril: é a pasta ser DE UM componente. Ela é, quando carrega
|
|
85
|
+
* o arquivo de mesmo nome - `Select/` com `SelectItem` e `SelectOption` dentro continua sendo
|
|
86
|
+
* a pasta do `Select` -, ou quando carrega UM componente só, que é o `Button/index.tsx` puro.
|
|
87
|
+
*/
|
|
88
|
+
if (eponymous)
|
|
89
|
+
return hasIndex ? "index" : "eponymous";
|
|
90
|
+
return hasIndex && componentNames.length <= 1 ? "index" : null;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* O EMPACOTAMENTO DE UM LUGAR, medido das pastas que estão DIRETAMENTE sob ele.
|
|
94
|
+
*
|
|
95
|
+
* `null` quando não há o que medir - menos de duas pastas com componente, ou nenhuma que
|
|
96
|
+
* resolva. Um lugar sem empacotamento observável não recebe campo, em vez de receber zero: um
|
|
97
|
+
* zero pelado aqui leria como "o projeto dele não embala nada", e o que é verdade é que a
|
|
98
|
+
* nossa leitura não viu.
|
|
99
|
+
*/
|
|
100
|
+
export function packagingOf(entries,
|
|
101
|
+
/** O lugar, como a caminhada o escreve - `"."` para a raiz do escopo. */
|
|
102
|
+
root) {
|
|
103
|
+
/**
|
|
104
|
+
* TODAS AS PASTAS SOB O LUGAR, e não só as filhas diretas - o primeiro rascunho errava aqui.
|
|
105
|
+
*
|
|
106
|
+
* Medido: com as filhas diretas, o `SignalUI` não era encontrado. A forma reconhecida ali é
|
|
107
|
+
* `atomic@src/lib/SignalUI`, e as filhas diretas dela são `atoms`, `molecules`, `organisms` -
|
|
108
|
+
* que não seguram componente NENHUM diretamente: os componentes moram um degrau abaixo, em
|
|
109
|
+
* `atoms/Button/Button.tsx`. A pergunta é sobre as pastas onde o componente MORA, e elas
|
|
110
|
+
* podem estar a qualquer profundidade sob o lugar.
|
|
111
|
+
*/
|
|
112
|
+
const kids = entries.filter((e) => e.direct > 0 && e.path !== root && isUnderPath(root, e.path));
|
|
113
|
+
if (kids.length < 2)
|
|
114
|
+
return null;
|
|
115
|
+
const resolving = kids.filter((c) => c.resolves !== null);
|
|
116
|
+
if (resolving.length === 0)
|
|
117
|
+
return null;
|
|
118
|
+
const byIndex = resolving.filter((c) => c.resolves === "index").length;
|
|
119
|
+
return {
|
|
120
|
+
folders: kids.length,
|
|
121
|
+
resolving: resolving.length,
|
|
122
|
+
file: byIndex === resolving.length
|
|
123
|
+
? "index"
|
|
124
|
+
: byIndex === 0
|
|
125
|
+
? "eponymous"
|
|
126
|
+
: "mixed",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* A SEGUNDA FRASE DA REGRA - o que o agente precisa saber para criar o próximo arquivo.
|
|
131
|
+
*
|
|
132
|
+
* `null` quando o empacotamento NÃO é a convenção do lugar. O limiar é a maioria simples, e
|
|
133
|
+
* ele está aqui em vez de dentro da medição de propósito: a maioria é o que torna a frase
|
|
134
|
+
* verdadeira como REGRA ("é assim que se faz aqui"), e abaixo dela os dois números continuam
|
|
135
|
+
* gravados como fato sem virar instrução.
|
|
136
|
+
*
|
|
137
|
+
* O QUE ELA NÃO AFIRMA, e cada omissão é uma medição que não temos: nada sobre onde a story ou
|
|
138
|
+
* o teste moram - a caminhada os exclui desde 0.16.337, então nunca os contamos; e nada sobre
|
|
139
|
+
* haver um arquivo só por pasta - o caso de uma pasta com o componente MAIS as partes dele é
|
|
140
|
+
* real, e a régua o aceita de propósito.
|
|
141
|
+
*/
|
|
142
|
+
export function packagingRule(p) {
|
|
143
|
+
if (p.resolving * 2 <= p.folders)
|
|
144
|
+
return null;
|
|
145
|
+
const how = p.file === "index"
|
|
146
|
+
? "each one holding an `index` that re-exports it"
|
|
147
|
+
: p.file === "eponymous"
|
|
148
|
+
? "each one holding a file of the same name"
|
|
149
|
+
: "some holding an `index` that re-exports it, some a file of the same name";
|
|
150
|
+
return `A new component starts a folder of its own, named after it, ${how} - ${p.resolving} of the ${p.folders} folders here are written that way. Whatever belongs only to that component goes in the same folder; a \`SelectItem\` beside \`Select\` is still one folder, not two. This says nothing about where a story or a test lives - the reading never counted those.`;
|
|
151
|
+
}
|
|
55
152
|
/**
|
|
56
153
|
* A MESMA ORGANIZAÇÃO VISTA DE MAIS PERTO NÃO É UMA SEGUNDA ESCOLHA (`INV-COB-09`).
|
|
57
154
|
*
|
|
@@ -173,9 +270,12 @@ export function detectArchitectures(entries) {
|
|
|
173
270
|
return dropShapesAlreadyExplained(found).sort((a, b) => b.files - a.files || a.root.localeCompare(b.root));
|
|
174
271
|
}
|
|
175
272
|
/** The sentence that becomes the rule. Written for an agent about to add a file. */
|
|
176
|
-
export function describeArchitecture(a) {
|
|
273
|
+
export function describeArchitecture(a, scope) {
|
|
274
|
+
const where = pathUnderScope(a.root, scope);
|
|
177
275
|
const shape = SHAPES.find((s) => s.kind === a.kind);
|
|
178
|
-
return shape
|
|
276
|
+
return shape
|
|
277
|
+
? shape.describe(a, where)
|
|
278
|
+
: `Components live under \`${where}\`.`;
|
|
179
279
|
}
|
|
180
280
|
/**
|
|
181
281
|
* The rule itself, in the shape `reading.rules` holds.
|
|
@@ -186,6 +286,15 @@ export function describeArchitecture(a) {
|
|
|
186
286
|
* true once, not a habit that needs a third sighting.
|
|
187
287
|
*/
|
|
188
288
|
export function architectureRule(a,
|
|
289
|
+
/**
|
|
290
|
+
* COMO O COMPONENTE É EMBALADO ALI - a segunda frase, quando ela é a convenção do lugar.
|
|
291
|
+
*
|
|
292
|
+
* OBRIGATÓRIO, e `null` é uma resposta: sem isso a regra volta a dizer só onde o componente
|
|
293
|
+
* nasce, num repositório onde todo componente é uma pasta - e o agente cria `atoms/Toast.tsx`
|
|
294
|
+
* onde o projeto inteiro escreve `atoms/Toast/Toast.tsx`. Um argumento opcional aqui seria a
|
|
295
|
+
* família do `fixReach`: o chamador esquece, `tsc` aceita, e a regra sai pela metade.
|
|
296
|
+
*/
|
|
297
|
+
packaging,
|
|
189
298
|
/**
|
|
190
299
|
* O ESCOPO, para a regra dizer um caminho que existe. `a.root` é relativo ao escopo, e uma
|
|
191
300
|
* regra no CLAUDE.md de um monorepo que diz `src/lib/SignalUI` manda o agente para um caminho
|
|
@@ -193,12 +302,14 @@ export function architectureRule(a,
|
|
|
193
302
|
*/
|
|
194
303
|
scope) {
|
|
195
304
|
const home = componentHome(a, scope);
|
|
305
|
+
const where = pathUnderScope(a.root, scope);
|
|
306
|
+
const packs = packaging ? packagingRule(packaging) : null;
|
|
196
307
|
return {
|
|
197
|
-
text: `${describeArchitecture(a)} A new component goes under \`${home}
|
|
308
|
+
text: `${describeArchitecture(a, scope)} A new component goes under \`${home}\`.${packs ? ` ${packs}` : ""}`,
|
|
198
309
|
applies: [],
|
|
199
310
|
kind: "implementation",
|
|
200
311
|
fact: true,
|
|
201
|
-
evidence: `${a.evidence.join("/")} under ${
|
|
312
|
+
evidence: `${a.evidence.join("/")} under ${where}, holding ${a.files} component file${a.files === 1 ? "" : "s"}`,
|
|
202
313
|
};
|
|
203
314
|
}
|
|
204
315
|
/**
|
|
@@ -215,10 +326,30 @@ scope) {
|
|
|
215
326
|
* na frente é ambíguo num monorepo, e é para o CLAUDE.md que essa linha vai.
|
|
216
327
|
*/
|
|
217
328
|
export function componentHome(a, scope) {
|
|
218
|
-
const
|
|
219
|
-
const tidy = base.replace(/\/\.$/, "").replace(/^\.\//, "");
|
|
329
|
+
const tidy = pathUnderScope(a.root, scope);
|
|
220
330
|
return a.evidence.length > 0 ? `${tidy}/{${a.evidence.join(", ")}}` : tidy;
|
|
221
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* O LUGAR, COMPOSTO UMA VEZ - a régua de caminho que todas as superfícies usam.
|
|
334
|
+
*
|
|
335
|
+
* O QUE O CLIENTE GANHA: o caminho que ele lê na regra, na evidência e na pergunta é sempre o
|
|
336
|
+
* mesmo, e sempre resolve a partir da raiz do repositório dele.
|
|
337
|
+
*
|
|
338
|
+
* ELA ERA DUAS, E AS DUAS DISCORDAVAM. `componentHome` normalizava barra final, `./` na frente e
|
|
339
|
+
* `/.` no fim; a `evidence` concatenava cru. Medido em 31/08, com o escopo real de um monorepo:
|
|
340
|
+
*
|
|
341
|
+
* scope="packages/ui/" root="src/components" evidência dizia packages/ui//src/components
|
|
342
|
+
* scope="packages/ui" root="." evidência dizia packages/ui/.
|
|
343
|
+
*
|
|
344
|
+
* A raiz do escopo é `"."` pela caminhada (`import.ts` escreve `path || "."`), então o segundo
|
|
345
|
+
* caso não é defensivo: é a organização reconhecida na raiz de um pacote, que é comum. Duas
|
|
346
|
+
* redações da mesma régua são duas réguas no dia em que uma for editada, e neste caso elas já
|
|
347
|
+
* tinham sido.
|
|
348
|
+
*/
|
|
349
|
+
export function pathUnderScope(root, scope) {
|
|
350
|
+
const base = scope ? `${scope.replace(/\/+$/, "")}/${root}` : root;
|
|
351
|
+
return base.replace(/\/\.$/, "").replace(/^\.\//, "");
|
|
352
|
+
}
|
|
222
353
|
/**
|
|
223
354
|
* A MESMA FORMA EM UMA FRASE - o que decide, e o número que a torna credível.
|
|
224
355
|
*
|
|
@@ -370,7 +501,20 @@ export const GAP_MIN_FILES = 3;
|
|
|
370
501
|
* Devolve `null` quando há forma reconhecida (aí quem fala é `describeChoice`) e quando o
|
|
371
502
|
* escopo é pequeno demais para ter organização (`GAP_MIN_FILES`).
|
|
372
503
|
*/
|
|
373
|
-
export function architectureGap(entries, found
|
|
504
|
+
export function architectureGap(entries, found,
|
|
505
|
+
/**
|
|
506
|
+
* ONDE ESTA LEITURA MEDIU - `"."` para a raiz, ou o caminho apontado. OBRIGATÓRIO.
|
|
507
|
+
*
|
|
508
|
+
* Não tem default de propósito: um default aqui devolve o parâmetro opcional que a frase
|
|
509
|
+
* tinha, e com ele um chamador podia esquecer o lugar sem que nada reprovasse.
|
|
510
|
+
*/
|
|
511
|
+
scope,
|
|
512
|
+
/**
|
|
513
|
+
* A PASTA NOVA DESTE ESCOPO - `proposeNewHome` sobre a MESMA árvore, ou `null`.
|
|
514
|
+
*
|
|
515
|
+
* Obrigatório pelo mesmo motivo de `scope`: uma proposta de outro lugar é pior que nenhuma.
|
|
516
|
+
*/
|
|
517
|
+
newHome) {
|
|
374
518
|
if (found.length > 0)
|
|
375
519
|
return null;
|
|
376
520
|
/**
|
|
@@ -388,27 +532,61 @@ export function architectureGap(entries, found) {
|
|
|
388
532
|
}
|
|
389
533
|
if (files < GAP_MIN_FILES)
|
|
390
534
|
return null;
|
|
391
|
-
|
|
535
|
+
/**
|
|
536
|
+
* O EMPACOTAMENTO NÃO ENTRA AQUI, e a ausência é decisão declarada.
|
|
537
|
+
*
|
|
538
|
+
* A leitura sabe medi-lo mesmo num escopo mudo, e o cliente que menos tem regra é justamente
|
|
539
|
+
* esse. Mas o caminho pelo qual o empacotamento alcança alguém é `architectures[].rule`, que
|
|
540
|
+
* uma lacuna não tem: gravá-lo aqui criaria um campo sem leitor - o defeito exato que esta
|
|
541
|
+
* mesma entrega existe para consertar um campo ao lado. Ele entra no dia em que a lacuna
|
|
542
|
+
* ganhar a frase que o carrega, e entra com o leitor junto.
|
|
543
|
+
*/
|
|
544
|
+
return {
|
|
545
|
+
scope,
|
|
546
|
+
files,
|
|
547
|
+
folders,
|
|
548
|
+
reason: gapReason({ scope, files, folders }),
|
|
549
|
+
newHome,
|
|
550
|
+
};
|
|
392
551
|
}
|
|
393
552
|
/**
|
|
394
|
-
*
|
|
553
|
+
* O LUGAR, EM PALAVRAS - a única tradução de `scope` para prosa neste produto.
|
|
395
554
|
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
555
|
+
* `"."` não resolve para nada na cabeça de quem lê um relatório, e "here" não resolve para nada
|
|
556
|
+
* na cabeça de quem rodou a leitura sobre uma parte de um monorepo. Uma leitura da raiz não tem
|
|
557
|
+
* outro lugar a que se referir, então ela é a única que pode dizer "here".
|
|
399
558
|
*/
|
|
400
|
-
|
|
559
|
+
function whereWeRead(scope) {
|
|
560
|
+
return scope === null || isRoot(scope) ? "here" : `under \`${scope}\``;
|
|
561
|
+
}
|
|
401
562
|
/**
|
|
402
|
-
*
|
|
563
|
+
* O QUE MEDIMOS, E DE QUEM É O LIMITE - a metade da frase que sobrevive a ser GRAVADA.
|
|
403
564
|
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
565
|
+
* As duas orações que são verdade para sempre sobre aquela leitura: os números medidos, e que
|
|
566
|
+
* a régua é nossa. Nada aqui tem data de validade, e é por isso que esta é a parte que vai
|
|
567
|
+
* para o censo e para a pergunta que o agente faz.
|
|
568
|
+
*
|
|
569
|
+
* A segunda oração não é gentileza: sem ela a frase lê como um defeito do repositório dele, e
|
|
570
|
+
* o defeito é da régua.
|
|
408
571
|
*/
|
|
409
|
-
|
|
572
|
+
export function gapReason(g) {
|
|
410
573
|
const files = `${g.files} component file${g.files === 1 ? "" : "s"}`;
|
|
411
574
|
const folders = `${g.folders} folder${g.folders === 1 ? "" : "s"}`;
|
|
412
|
-
|
|
413
|
-
|
|
575
|
+
return `We read ${files} in ${folders} ${whereWeRead(g.scope)}, 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.`;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* A FRASE INTEIRA, para o TERMINAL - a declaração gravável mais a consequência de hoje.
|
|
579
|
+
*
|
|
580
|
+
* A ordem é a da dúvida de quem lê: o que medimos, de quem é o limite, e o que isso custa a ele
|
|
581
|
+
* agora. As duas primeiras vêm de `gapReason` e são as mesmas que o censo carrega - uma redação
|
|
582
|
+
* só, e não duas que divergem no dia em que alguém editar uma.
|
|
583
|
+
*
|
|
584
|
+
* A TERCEIRA MORA SÓ AQUI, e é a razão de esta função existir separada. Ela afirma o estado do
|
|
585
|
+
* `CLAUDE.md` DELE, o que é verdade no instante do relatório e falso depois - inclusive falso
|
|
586
|
+
* por causa da pergunta que este mesmo relatório abre. Gravada no censo, ela viraria medição
|
|
587
|
+
* corrente promovida a fato permanente, e seria reescrita como nova a cada `sync` sobre um
|
|
588
|
+
* repositório que já tem a regra.
|
|
589
|
+
*/
|
|
590
|
+
export function describeGap(g) {
|
|
591
|
+
return `${g.reason} 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
592
|
}
|
package/dist/install-marks.js
CHANGED
|
@@ -415,7 +415,76 @@ export const CHECKER_SINCE = "0.16.308";
|
|
|
415
415
|
* NÃO se moveu, e isso é a informação: nenhum dos 28 apps dourados tem forma repetindo o que uma
|
|
416
416
|
* ancestral já diz, então a estabilidade ali prova ausência de regressão e não ausência de efeito.
|
|
417
417
|
*/
|
|
418
|
-
|
|
418
|
+
/**
|
|
419
|
+
* 0.16.338 -> 0.16.339 em 31/08 (`INV-COB-08`): a lacuna de organização passou a viajar no censo
|
|
420
|
+
* como LISTA, com o lugar de cada leitura e a frase pronta que a declara - `architectureGaps[]`,
|
|
421
|
+
* no lugar do par somado `architectureGap`.
|
|
422
|
+
*
|
|
423
|
+
* A marca sobe porque o AGENTE dele é o leitor. A skill `sui-import-ds` copia
|
|
424
|
+
* `architectureGaps[].reason` verbatim para abrir a pergunta de onde nasce um componente novo, e
|
|
425
|
+
* um censo medido antes não carrega esse campo: o agente volta ao estado em que a leitura rodou,
|
|
426
|
+
* não reconheceu nada, e nenhuma pergunta chegou à tela dele. O par antigo também não guarda o
|
|
427
|
+
* LUGAR da medição, e nada pode recuperá-lo sem reler o disco - a caminhada é o que sabe onde
|
|
428
|
+
* cada arquivo estava.
|
|
429
|
+
*
|
|
430
|
+
* O QUE O `sync` ENTREGA: ele remede com `takeCensus`, então o censo em disco e o reenviado já
|
|
431
|
+
* nascem com a lista e a frase. O que ele NÃO entrega continua sendo a linha do `CLAUDE.md` dele,
|
|
432
|
+
* pelo mesmo motivo da marca anterior - `rulesFromCensus` não olha para este campo, e a regra de
|
|
433
|
+
* organização nasce da conversa de import.
|
|
434
|
+
*
|
|
435
|
+
* MEDIÇÃO de 31/08, no banco de produção: **1 sistema com censo de import guardado, e `0` com a
|
|
436
|
+
* forma antiga** - aquele censo é anterior a 0.16.337 e não carrega campo de organização nenhum.
|
|
437
|
+
* Foto daquela população naquele dia, nunca invariante.
|
|
438
|
+
*
|
|
439
|
+
* A COMPATIBILIDADE NÃO É O QUE ALCANÇA ELE, e a distinção foi medida por uma revisão de QA. A
|
|
440
|
+
* leitura da forma antiga (`architectureGaps`, em `merge-census.ts`) roda em UM caminho:
|
|
441
|
+
* `import --census <arquivo>`, onde um agente reenvia um censo que ele mesmo anotou. A FUSÃO
|
|
442
|
+
* nunca a alcança - ela só vê censos que `takeCensus` acabou de medir, e esses já nascem na forma
|
|
443
|
+
* nova. Quem tem `census.json` em disco e não usa aquele comando é alcançado pelo `sync`, que
|
|
444
|
+
* remede e reescreve o arquivo: é isso, e não a compatibilidade, que esta marca anuncia.
|
|
445
|
+
*/
|
|
446
|
+
/**
|
|
447
|
+
* 0.16.339 -> 0.16.340 em 31/08 (`INV-COB-10`): a leitura passou a medir COMO o componente é
|
|
448
|
+
* embalado - se as pastas sob um lugar resolvem como o módulo dele, por barril `index.*` ou por
|
|
449
|
+
* arquivo de mesmo nome módulo caixa e separador - e a regra que vai para o `CLAUDE.md` dele
|
|
450
|
+
* ganhou uma segunda frase dizendo isso.
|
|
451
|
+
*
|
|
452
|
+
* A marca sobe porque o fato nasce da CAMINHADA. Um censo medido antes não carrega `resolves`
|
|
453
|
+
* por pasta nem `packaging` por lugar, e nada aqui pode recuperá-los sem reler o disco: o
|
|
454
|
+
* `index.ts` que faz `ActivityRing/` resolver como módulo não é arquivo de componente e nunca
|
|
455
|
+
* foi contado. Só a remedição vê.
|
|
456
|
+
*
|
|
457
|
+
* O QUE O CLIENTE GANHA: hoje a regra dele diz onde o próximo componente nasce e cala sobre o
|
|
458
|
+
* empacotamento. O agente cria `atoms/Toast.tsx` num repositório onde tudo é
|
|
459
|
+
* `atoms/Toast/Toast.tsx` com `index.ts` ao lado, e uma pessoa move o arquivo toda vez. A regra
|
|
460
|
+
* estava certa e incompleta.
|
|
461
|
+
*
|
|
462
|
+
* O `sync` remede com `takeCensus`, então o censo em disco e o reenviado já nascem com os dois
|
|
463
|
+
* campos. O que ele NÃO faz continua sendo reescrever a regra já escrita no `CLAUDE.md` dele.
|
|
464
|
+
*
|
|
465
|
+
* MEDIÇÃO de 31/08, duas populações: 35 formas reconhecidas ganham a segunda frase, 3 têm o
|
|
466
|
+
* fato e ficam abaixo da maioria, 37 não têm empacotamento observável.
|
|
467
|
+
*/
|
|
468
|
+
/**
|
|
469
|
+
* 0.16.340 -> 0.16.341 em 31/08 (`INV-COB-11`): o caminho que a regra de organização cita passou
|
|
470
|
+
* a resolver a partir da raiz do repositório, nas QUATRO superfícies que o dizem.
|
|
471
|
+
*
|
|
472
|
+
* O QUE O CLIENTE DE MONOREPO VIVIA: a regra que governa todo prompt dele se contradizia na mesma
|
|
473
|
+
* frase - *"Components live in an atomic ladder under `src/components`. (...) A new component goes
|
|
474
|
+
* under `packages/ui/src/components/{…}`"*. A primeira metade é a que descreve o projeto, e ela
|
|
475
|
+
* mandava o agente procurar uma pasta que não existe na raiz.
|
|
476
|
+
*
|
|
477
|
+
* A marca sobe porque o campo é COMPOSTO na medição: `architectures[].rule.text` já está gravado
|
|
478
|
+
* com o caminho pelado, e nada do lado de cá o reescreve. O `sync` remede com `takeCensus`, então
|
|
479
|
+
* quem rodar recebe a regra certa no censo em disco e no reenviado; sem a marca, o `align` não
|
|
480
|
+
* chamaria ninguém para rodar.
|
|
481
|
+
*
|
|
482
|
+
* QUEM NÃO É AFETADO, e é a maioria: quem mede a raiz do repositório. Sem escopo o caminho já era
|
|
483
|
+
* o mesmo nas quatro superfícies - o defeito só existe quando a leitura foi apontada para uma
|
|
484
|
+
* parte do repositório. O corpus dourado NÃO se moveu por isso: nenhum dos 29 apps é medido com
|
|
485
|
+
* escopo, e o hash parado aqui é ausência de cobertura, nunca ausência de efeito.
|
|
486
|
+
*/
|
|
487
|
+
export const READER_SINCE = "0.16.341";
|
|
419
488
|
/**
|
|
420
489
|
* O QUE ESTÁ INSTALADO AQUI FICOU PARA TRÁS - e as DUAS condições que fazem isso ser verdade.
|
|
421
490
|
*
|
package/dist/merge-census.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { gapReason } from "./doctor/architecture.js";
|
|
1
2
|
const asRecord = (v) => (v ?? {});
|
|
2
3
|
/** Nome do escopo para as mensagens - `.` quando a medição não escopou nada. */
|
|
3
4
|
const nameOf = (c, i) => c.scope ?? (i === 0 ? "." : `#${i + 1}`);
|
|
@@ -120,22 +121,30 @@ export function mergeCensus(list) {
|
|
|
120
121
|
let conventions = first.conventions ?? undefined;
|
|
121
122
|
let architectures = first.architectures ?? undefined;
|
|
122
123
|
/**
|
|
123
|
-
*
|
|
124
|
+
* AS LACUNAS DE ORGANIZAÇÃO, CONCATENADAS - `INV-COB-08`, ver `architectureGaps` em
|
|
125
|
+
* `import.ts`.
|
|
124
126
|
*
|
|
125
127
|
* A declaração de qualquer escopo SOBREVIVE à fusão. Ela diz "medimos aqui e não soubemos
|
|
126
128
|
* nomear"; deixá-la de fora porque o outro escopo foi reconhecido devolve o silêncio que a
|
|
127
129
|
* invariante existe para eliminar, um passo adiante - e o cliente que rodou com dois escopos
|
|
128
130
|
* é justamente quem tem mais lugar onde a resposta pode sumir.
|
|
129
131
|
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
132
|
+
* CONCATENADAS, E NUNCA SOMADAS, e este era o defeito: contagem é aditiva, mas contagem COM
|
|
133
|
+
* UM LUGAR não é. Somando, dois escopos mudos de 37 e 7 arquivos produziam `{files: 44}` com
|
|
134
|
+
* a mesma forma de uma leitura só - e a frase construída sobre aquilo diria "44 arquivos sob
|
|
135
|
+
* `packages/ui`", que é falso, ou "44 arquivos aqui", que não é conferível. O próprio tipo
|
|
136
|
+
* promete que o cliente confere os dois números com um `find` de uma linha sobre UMA pasta, e
|
|
137
|
+
* nenhum `find` devolve 44.
|
|
138
|
+
*
|
|
139
|
+
* É o mesmo defeito que `scope-of.ts` já consertou um campo adiante (`census.scope` era o
|
|
140
|
+
* PRIMEIRO escopo, e todo componente do segundo era atribuído a ele), e a mesma forma que
|
|
141
|
+
* `architectures` já usa: cada entrada carrega o seu lugar embutido.
|
|
142
|
+
*
|
|
143
|
+
* O PISO É POR ESCOPO, e a lista é o que o mantém assim. Dois escopos de 2 arquivos calam
|
|
144
|
+
* cada um por `GAP_MIN_FILES`; somados antes do piso, os 4 falariam sobre uma organização que
|
|
145
|
+
* nenhum dos dois tem.
|
|
134
146
|
*/
|
|
135
|
-
let
|
|
136
|
-
?.files;
|
|
137
|
-
let gapFolders = first.architectureGap
|
|
138
|
-
?.folders;
|
|
147
|
+
let gaps = architectureGaps(first);
|
|
139
148
|
/**
|
|
140
149
|
* ONDE O PRÓXIMO COMPONENTE VAI - o PRIMEIRO escopo vence, a mesma regra de `scope`.
|
|
141
150
|
*
|
|
@@ -203,11 +212,7 @@ export function mergeCensus(list) {
|
|
|
203
212
|
brokenRefs = concat(brokenRefs, c.brokenRefs);
|
|
204
213
|
conventions = concat(conventions, c.conventions);
|
|
205
214
|
architectures = concat(architectures, c.architectures);
|
|
206
|
-
|
|
207
|
-
if (gap) {
|
|
208
|
-
gapFiles = (gapFiles ?? 0) + (gap.files ?? 0);
|
|
209
|
-
gapFolders = (gapFolders ?? 0) + (gap.folders ?? 0);
|
|
210
|
-
}
|
|
215
|
+
gaps = [...gaps, ...architectureGaps(c)];
|
|
211
216
|
components = concat(components, c.components);
|
|
212
217
|
defined = concat(defined, c.defined);
|
|
213
218
|
for (const s of c.project.stack ?? [])
|
|
@@ -291,8 +296,8 @@ export function mergeCensus(list) {
|
|
|
291
296
|
out.conventions = conventions;
|
|
292
297
|
if (architectures && architectures.length > 0)
|
|
293
298
|
out.architectures = architectures;
|
|
294
|
-
if (
|
|
295
|
-
out.
|
|
299
|
+
if (gaps.length > 0)
|
|
300
|
+
out.architectureGaps = gaps;
|
|
296
301
|
if (newComponentHome)
|
|
297
302
|
out.newComponentHome = newComponentHome;
|
|
298
303
|
if (components && components.length > 0)
|
|
@@ -357,3 +362,52 @@ export function mergeCensus(list) {
|
|
|
357
362
|
}
|
|
358
363
|
return out;
|
|
359
364
|
}
|
|
365
|
+
/**
|
|
366
|
+
* AS LACUNAS DE UM CENSO, NAS DUAS FORMAS QUE EXISTEM - `INV-COB-08`.
|
|
367
|
+
*
|
|
368
|
+
* O QUE O CLIENTE GANHA: quem entrega um censo JÁ MEDIDO continua recebendo a declaração de que
|
|
369
|
+
* a leitura rodou e não reconheceu nada. Descartar a forma antiga devolveria a esse cliente o
|
|
370
|
+
* silêncio exato que a invariante existe para eliminar, e ele nem saberia que houve leitura.
|
|
371
|
+
*
|
|
372
|
+
* ONDE ISTO É ALCANÇADO, e não é aqui dentro: a fusão só vê censos que `takeCensus` acabou de
|
|
373
|
+
* medir no mesmo processo, e esses já nascem na forma nova. O caminho por onde um censo ANTIGO
|
|
374
|
+
* entra de verdade é `import --census <arquivo>`, que lê o JSON do disco e o usa como está - o
|
|
375
|
+
* comando que a nossa própria skill ensina a um agente que anotou o censo. Uma revisão de QA
|
|
376
|
+
* mediu que este ramo só tinha o spec como chamador; ele agora roda naquele caminho, e por isso
|
|
377
|
+
* é exportado.
|
|
378
|
+
*
|
|
379
|
+
* A FORMA ANTIGA NÃO GUARDA O LUGAR, e nada aqui pode inventá-lo. Um censo de 0.16.337 ou
|
|
380
|
+
* 0.16.338 traz `architectureGap: {files, folders}` e nada mais - possivelmente já somado de
|
|
381
|
+
* dois escopos pela versão anterior desta função. Assumir a raiz produziria uma frase apontando
|
|
382
|
+
* para um lugar onde a contagem não fecha, que é pior que dizer que não sabemos. `null` é o
|
|
383
|
+
* terceiro estado do campo, e ele NÃO é a raiz: a frase resultante diz "here", que é a redação
|
|
384
|
+
* honesta para uma leitura sem lugar registrado, e o dado continua dizendo que não sabemos.
|
|
385
|
+
*
|
|
386
|
+
* `reason` é RECOMPOSTO e nunca herdado, porque a frase antiga foi escrita com um argumento de
|
|
387
|
+
* escopo que este censo não guardou.
|
|
388
|
+
*/
|
|
389
|
+
export function architectureGaps(c) {
|
|
390
|
+
const anyish = c;
|
|
391
|
+
const fresh = anyish.architectureGaps;
|
|
392
|
+
if (Array.isArray(fresh))
|
|
393
|
+
return fresh;
|
|
394
|
+
const legacy = anyish.architectureGap;
|
|
395
|
+
if (!legacy)
|
|
396
|
+
return [];
|
|
397
|
+
const files = legacy.files ?? 0;
|
|
398
|
+
const folders = legacy.folders ?? 0;
|
|
399
|
+
const scope = null;
|
|
400
|
+
return [
|
|
401
|
+
{
|
|
402
|
+
scope,
|
|
403
|
+
files,
|
|
404
|
+
folders,
|
|
405
|
+
reason: gapReason({ scope, files, folders }),
|
|
406
|
+
/**
|
|
407
|
+
* A FORMA ANTIGA NÃO GUARDOU A PROPOSTA, e o `newComponentHome` do topo é o do PRIMEIRO
|
|
408
|
+
* escopo - oferecê-lo aqui apontaria a pergunta para um lugar que pode não ser este.
|
|
409
|
+
*/
|
|
410
|
+
newHome: null,
|
|
411
|
+
},
|
|
412
|
+
];
|
|
413
|
+
}
|
package/package.json
CHANGED