synthesisui 0.16.381 → 0.16.383
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 +41 -3
- package/dist/commands/sync.js +13 -1
- package/dist/doctor/style-ledger.js +55 -10
- package/dist/install-marks.js +12 -1
- package/package.json +2 -2
- package/dist/mcp-approval.js +0 -111
- package/dist/memory/recall.js +0 -161
- package/dist/memory/work-state.js +0 -131
package/dist/commands/import.js
CHANGED
|
@@ -35,7 +35,7 @@ import { parseSchemeBlocks } from "../doctor/scheme-blocks.js";
|
|
|
35
35
|
import { describeSignals, emptySignals, finishSignals, readSignalsInto, } from "../doctor/signals.js";
|
|
36
36
|
import { sketchOf } from "../doctor/sketch.js";
|
|
37
37
|
import { describeForeign, readStyleIslands, } from "../doctor/style-island.js";
|
|
38
|
-
import { buildLedger, describeLedger, } from "../doctor/style-ledger.js";
|
|
38
|
+
import { buildLedger, describeLedger, sampledForUpload, } from "../doctor/style-ledger.js";
|
|
39
39
|
import { MUI_DEFAULT_SPACING, readStyledComponents, spacingOf, } from "../doctor/style-props.js";
|
|
40
40
|
import { buildTable } from "../doctor/tokens.js";
|
|
41
41
|
import { definitionSpan, parseClass, readInlineStyle, rootClasses, rootTag, transcribe, } from "../doctor/transcribe.js";
|
|
@@ -419,6 +419,32 @@ const SKIPPED_CAP = 4096;
|
|
|
419
419
|
* envelhecer sozinha significa dois denominadores diferentes para a mesma palavra na mesma saída.
|
|
420
420
|
*/
|
|
421
421
|
const SHOWS_A_COMPONENT = /(\.(spec|test|stories)\.[a-z]+$|__tests__\/|(^|\/)\.storybook\/)/;
|
|
422
|
+
/**
|
|
423
|
+
* O NOME DO REPOSITÓRIO DE ONDE ESTA MEDIÇÃO SAIU - e não o da pasta que ela varreu.
|
|
424
|
+
*
|
|
425
|
+
* O QUE ACONTECIA: em modo biblioteca o chamador mede `join(root, "packages/ui")`, então a
|
|
426
|
+
* procedência dizia `ui`. Todo monorepo do mundo tem um `packages/ui`, e o campo que existe
|
|
427
|
+
* exatamente para distinguir dois repositórios parecidos passava a não distinguir nenhum -
|
|
428
|
+
* fazendo o oposto do que o comentário ao lado dele promete.
|
|
429
|
+
*
|
|
430
|
+
* MEDIDO em 05/09 pelo caminho real do `import --dry`, sobre uma cópia do repositório do dono:
|
|
431
|
+
* sem escopo grava `codelevel-monorepo`; com `--scope packages/ui` gravava `ui`. E o escopo é o
|
|
432
|
+
* caso do MONOREPO, que é justamente onde a ambiguidade existe.
|
|
433
|
+
*
|
|
434
|
+
* O ESCOPO JÁ VIAJA NO CAMPO DELE, então a raiz sai por subtração, e não por um parâmetro novo:
|
|
435
|
+
* um `projectRoot` opcional seria esquecido pelo terceiro chamador, e este campo é do tipo que
|
|
436
|
+
* ninguém confere. Quando o caminho não termina no escopo - um chamador que rotule de outro jeito
|
|
437
|
+
* -, o nome continua sendo o da pasta medida, que é o comportamento de sempre.
|
|
438
|
+
*/
|
|
439
|
+
function repoNameOf(root, scopeLabel) {
|
|
440
|
+
const full = resolve(root);
|
|
441
|
+
if (!scopeLabel)
|
|
442
|
+
return basename(full);
|
|
443
|
+
const suffix = sep + scopeLabel.split("/").join(sep);
|
|
444
|
+
if (!full.endsWith(suffix))
|
|
445
|
+
return basename(full);
|
|
446
|
+
return basename(full.slice(0, -suffix.length)) || basename(full);
|
|
447
|
+
}
|
|
422
448
|
export async function takeCensus(root, opts) {
|
|
423
449
|
/**
|
|
424
450
|
* O RELATÓRIO SÓ SAI QUANDO ALGUÉM O PEDIU - `say` cala quando o chamador é uma RE-medição.
|
|
@@ -2776,7 +2802,10 @@ export async function takeCensus(root, opts) {
|
|
|
2776
2802
|
* A PROCEDÊNCIA, GRAVADA - ver `measured` no tipo. `basename` e não o caminho: o nome da pasta
|
|
2777
2803
|
* distingue dois monorepos parecidos sem mandar o diretório pessoal dele para o nosso servidor.
|
|
2778
2804
|
*/
|
|
2779
|
-
measured: {
|
|
2805
|
+
measured: {
|
|
2806
|
+
repo: repoNameOf(root, scopeLabel),
|
|
2807
|
+
at: new Date().toISOString(),
|
|
2808
|
+
},
|
|
2780
2809
|
...(scopeLabel ? { scope: scopeLabel } : {}),
|
|
2781
2810
|
/**
|
|
2782
2811
|
* O DENOMINADOR DO FORA - ver `outside-scope.ts`.
|
|
@@ -4466,8 +4495,17 @@ export async function runImport(opts) {
|
|
|
4466
4495
|
* ler `_synthesisui/ds/<slug>/.lock`, que num import não existe de qualquer forma. Todo campo do
|
|
4467
4496
|
* payload é opcional, então o que falta simplesmente não viaja - em vez de o conjunto todo faltar.
|
|
4468
4497
|
*/
|
|
4498
|
+
/**
|
|
4499
|
+
* O QUE SOBE É AMOSTRA; O QUE FICA NO REPOSITÓRIO DELE É INTEIRO - `INV-COLETA-18`.
|
|
4500
|
+
*
|
|
4501
|
+
* `sampledForUpload` aplica o teto AQUI, depois de o censo já ter sido gravado em
|
|
4502
|
+
* `_synthesisui/census.json`. O tamanho deste corpo não muda em byte nenhum - é o mesmo 4 096
|
|
4503
|
+
* de sempre -, e o que muda é que o arquivo dele deixou de ser podado por um limite nosso.
|
|
4504
|
+
*/
|
|
4469
4505
|
body: JSON.stringify({
|
|
4470
|
-
census
|
|
4506
|
+
census: census.ledger
|
|
4507
|
+
? { ...census, ledger: sampledForUpload(census.ledger) }
|
|
4508
|
+
: census,
|
|
4471
4509
|
name: chosen,
|
|
4472
4510
|
...(opts.group ? { group: opts.group } : {}),
|
|
4473
4511
|
...(repoAtImport ? { repo: repoAtImport } : {}),
|
package/dist/commands/sync.js
CHANGED
|
@@ -6,6 +6,7 @@ import { readToken, resolveRegistry } from "../config.js";
|
|
|
6
6
|
import { inherit, readDeclaredForms, } from "../doctor/declared-forms.js";
|
|
7
7
|
import { markSent, readEvents } from "../doctor/ledger.js";
|
|
8
8
|
import { checkableName, closeRequest, readRequests, verifyAndCloseRequests, } from "../doctor/requests.js";
|
|
9
|
+
import { sampledForUpload } from "../doctor/style-ledger.js";
|
|
9
10
|
import { describeDelta, fingerprintReadings, readSyncMark, writeSyncMark, } from "../last-sync.js";
|
|
10
11
|
import { measuredScope, rememberScope } from "../measured-scope.js";
|
|
11
12
|
import { fromCensus } from "../memory/observation.js";
|
|
@@ -556,7 +557,18 @@ export async function remeasure(args) {
|
|
|
556
557
|
"content-type": "application/json",
|
|
557
558
|
Authorization: `Bearer ${token}`,
|
|
558
559
|
},
|
|
559
|
-
|
|
560
|
+
/**
|
|
561
|
+
* O QUE SOBE É AMOSTRA; O QUE FICA NO REPOSITÓRIO DELE É INTEIRO - `INV-COLETA-18`.
|
|
562
|
+
*
|
|
563
|
+
* A gravação em `_synthesisui/census.json` acontece acima, e ela leva o ledger COMPLETO: é dali
|
|
564
|
+
* que `gaps` e a skill `sui-reader` tiram a forma, o lugar e o texto de cada declaração que
|
|
565
|
+
* nenhum leitor entendeu. O teto vale só para o nosso lado, e o tamanho deste corpo não muda.
|
|
566
|
+
*/
|
|
567
|
+
body: JSON.stringify({
|
|
568
|
+
census: census.ledger
|
|
569
|
+
? { ...census, ledger: sampledForUpload(census.ledger) }
|
|
570
|
+
: census,
|
|
571
|
+
}),
|
|
560
572
|
}).catch(() => null);
|
|
561
573
|
if (!res?.ok) {
|
|
562
574
|
console.log(body(res
|
|
@@ -291,12 +291,11 @@ declared) {
|
|
|
291
291
|
* declarado e um teto silencioso. Inverter as duas linhas devolve o defeito.
|
|
292
292
|
*/
|
|
293
293
|
group.distinctTexts.add(f.text);
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
});
|
|
294
|
+
group.unreadable.push({
|
|
295
|
+
file: f.file,
|
|
296
|
+
line: f.line,
|
|
297
|
+
text: f.text.replace(/\s+/g, " ").trim().slice(0, 400),
|
|
298
|
+
});
|
|
300
299
|
}
|
|
301
300
|
groups.set(key, group);
|
|
302
301
|
}
|
|
@@ -413,17 +412,27 @@ values) {
|
|
|
413
412
|
*
|
|
414
413
|
* O QUE ELA NÃO FAZ é prometer o desfecho - mesma correção que `shape-not-read` recebeu em
|
|
415
414
|
* 24/08. Ela diz quantos textos viajam e o que isso significa para um leitor que a gente
|
|
416
|
-
* publique depois
|
|
417
|
-
*
|
|
415
|
+
* publique depois.
|
|
416
|
+
*
|
|
417
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
418
|
+
* E EM 06/09 ELA TROCOU DE SENTIDO, porque o teto trocou de lado - `INV-COLETA-18`.
|
|
419
|
+
*
|
|
420
|
+
* A frase antiga dizia *"de N textos, 4096 viajam"* sobre um arquivo que TAMBÉM estava podado:
|
|
421
|
+
* o corte acontecia na construção, então o `census.json` dele levava os mesmos 4096. Agora o
|
|
422
|
+
* arquivo dele leva todos, e o que é amostrado é só o que sobe para nós.
|
|
423
|
+
*
|
|
424
|
+
* A CONDIÇÃO PASSOU A SER O TETO, e não `unreadable.length`: aquele campo agora vale `distinct`
|
|
425
|
+
* sempre, então a comparação antiga nunca seria verdadeira e a frase sumiria em silêncio - uma
|
|
426
|
+
* lacuna declarada virando lacuna calada por efeito colateral, que é o oposto da lei 8.
|
|
418
427
|
*/
|
|
419
|
-
if (g.distinct !== undefined && g.distinct >
|
|
428
|
+
if (g.distinct !== undefined && g.distinct > DISTINCT_CAP)
|
|
420
429
|
lines.push(
|
|
421
430
|
/**
|
|
422
431
|
* `census` ERA O NOSSO NOME NA TELA DELE - trocado por "the measurement we already have",
|
|
423
432
|
* que é o que a palavra significa do lado dele. Pego pela régua de `INV-VOC-05` no dia em
|
|
424
433
|
* que `doctor/` entrou nela (03/09).
|
|
425
434
|
*/
|
|
426
|
-
`
|
|
435
|
+
` all ${g.distinct} distinct texts stay in the measurement file in your repository - the ${g.distinct - DISTINCT_CAP} past the first ${DISTINCT_CAP} did not travel to us, so a reader we publish later reaches those only after another scan`);
|
|
427
436
|
for (const e of g.examples)
|
|
428
437
|
lines.push(` ${e.file}:${e.line} ${e.text.slice(0, 80)}`);
|
|
429
438
|
}
|
|
@@ -445,3 +454,39 @@ export function describeValueRuler(values) {
|
|
|
445
454
|
`Of the ${values.seen} class declarations on your components, ${values.structure} are structure (layout plumbing, not design decisions). Of the ${decisions} design decisions, ${closed} are interpreted${values.answered > 0 ? ` (${values.answered} of them answered by you)` : ""} - ${percent}%.`,
|
|
446
455
|
];
|
|
447
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* O CORTE, APLICADO DEPOIS DA BIFURCAÇÃO - fecha o `pending` de `INV-COLETA-18`.
|
|
459
|
+
*
|
|
460
|
+
* A REGRA, do dono (05/09): *"coisas que a gente não interpreta a gente continua enviando para o
|
|
461
|
+
* usuário, para o agente saber o que fazer"*. Falhar em ler degrada a NOSSA metade; nunca o insumo
|
|
462
|
+
* dele.
|
|
463
|
+
*
|
|
464
|
+
* O QUE ACONTECIA. `buildLedger` cortava em `DISTINCT_CAP` durante a CONSTRUÇÃO, e o objeto podado
|
|
465
|
+
* era o mesmo que ia para os dois lados: gravado em `_synthesisui/census.json` na máquina dele E
|
|
466
|
+
* enviado ao nosso banco. Um teto que existe pelo NOSSO custo de armazenamento estava podando o
|
|
467
|
+
* arquivo de onde o agente DELE tira a forma, o lugar e o texto. Medido em 03/09, com o teto solto:
|
|
468
|
+
*
|
|
469
|
+
* web-subscribe component-not-admitted 9 722 distintos, 4 096 viajavam 58% fora
|
|
470
|
+
* web-subscribe shape-not-read 8 016 distintos, 4 096 viajavam 49% fora
|
|
471
|
+
* frontend-hub/dashboard component-not-admitted 6 394 distintos, 4 096 viajavam 36% fora
|
|
472
|
+
*
|
|
473
|
+
* AGORA A CONSTRUÇÃO GUARDA TUDO e quem chama decide. O disco dele recebe o ledger inteiro; o corpo
|
|
474
|
+
* do POST passa por aqui. O tamanho do que sobe não muda em byte nenhum - é o mesmo 4 096 de antes.
|
|
475
|
+
*
|
|
476
|
+
* E `distinct` NÃO É RECALCULADO aqui, de propósito: ele conta os distintos que a MEDIÇÃO viu, e é
|
|
477
|
+
* exatamente a diferença entre ele e `unreadable.length` que diz ao servidor que a lista recebida é
|
|
478
|
+
* amostra. Recalcular apagaria a única evidência do corte - o defeito que `INV-COB-14` protege.
|
|
479
|
+
*
|
|
480
|
+
* O TETO DAS FORMAS entra junto porque responde à mesma pergunta e ao mesmo custo: `formsTotal`
|
|
481
|
+
* continua dizendo quantas existem.
|
|
482
|
+
*/
|
|
483
|
+
export function sampledForUpload(ledger) {
|
|
484
|
+
return {
|
|
485
|
+
...ledger,
|
|
486
|
+
unread: ledger.unread.map((group) => ({
|
|
487
|
+
...group,
|
|
488
|
+
unreadable: group.unreadable.slice(0, DISTINCT_CAP),
|
|
489
|
+
...(group.forms ? { forms: group.forms.slice(0, FORM_CAP) } : {}),
|
|
490
|
+
})),
|
|
491
|
+
};
|
|
492
|
+
}
|
package/dist/install-marks.js
CHANGED
|
@@ -680,7 +680,18 @@ export const CHECKER_SINCE = "0.16.378";
|
|
|
680
680
|
* posição de conteúdo. Um censo medido antes desta versão conta como deriva o que a folha do app
|
|
681
681
|
* nem alcança - e o número do cliente é a promessa.
|
|
682
682
|
*/
|
|
683
|
-
|
|
683
|
+
/**
|
|
684
|
+
* 0.16.381 -> 0.16.383 em 06/09: o censo produzido MUDA, e do lado DELE. `DISTINCT_CAP` cortava os
|
|
685
|
+
* textos durante a construção do ledger, então o mesmo objeto podado era gravado em
|
|
686
|
+
* `_synthesisui/census.json` na máquina dele e enviado para nós - um teto que existe pelo NOSSO
|
|
687
|
+
* custo podando o insumo DELE (`INV-COLETA-18`). Agora `buildLedger` guarda tudo e o corte é
|
|
688
|
+
* aplicado só no corpo do POST. Um censo medido antes desta versão tem o arquivo dele podado em
|
|
689
|
+
* todo grupo acima de 4 096 textos distintos, e só um `sync` traz o resto.
|
|
690
|
+
* QUEM NÃO É AFETADO: quem não tem nenhum grupo acima de 4 096 - o `sync` daquele repositório não
|
|
691
|
+
* muda um byte. Medido em 06/09: `frontend-hub/apps/web-dashboard` tem UM grupo acima (6 393), e
|
|
692
|
+
* `packages/ui` e `apps/web-review` nenhum.
|
|
693
|
+
*/
|
|
694
|
+
export const READER_SINCE = "0.16.383";
|
|
684
695
|
/**
|
|
685
696
|
* O QUE ESTÁ INSTALADO AQUI FICOU PARA TRÁS - e as DUAS condições que fazem isso ser verdade.
|
|
686
697
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "synthesisui",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.383",
|
|
4
4
|
"description": "Bring SynthesisUI design systems into any project - tokens, typed components, whole pages and an agent-ready CLAUDE.md manifest.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"node": ">=18"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
|
-
"build": "tsc -p tsconfig.json && chmod +x dist/index.js",
|
|
32
|
+
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json && chmod +x dist/index.js",
|
|
33
33
|
"dev": "tsx src/index.ts",
|
|
34
34
|
"prepublishOnly": "npm run build",
|
|
35
35
|
"test": "vitest run",
|
package/dist/mcp-approval.js
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
/**
|
|
4
|
-
* AS FERRAMENTAS DECLARADAS E ESPERANDO APROVAÇÃO - o portão que matou um import inteiro.
|
|
5
|
-
*
|
|
6
|
-
* O QUE O CLIENTE GANHA: a sessão em que a aprovação está pendente é a sessão que lhe diz isso.
|
|
7
|
-
* Antes ele descobria no meio do import, quando o agente já tinha medido o repositório e não tinha
|
|
8
|
-
* como continuar.
|
|
9
|
-
*
|
|
10
|
-
* MEDIDO em 03/09, no segundo import real do dono. Ele rodou `import my design system`, o agente
|
|
11
|
-
* mediu 262 arquivos, leu 78 componentes, descreveu a paleta dele - e parou:
|
|
12
|
-
*
|
|
13
|
-
* synthesisui: npx synthesisui@latest mcp - ⏸ Pending approval
|
|
14
|
-
*
|
|
15
|
-
* O playbook do import é SERVIDO pelo MCP, então sem aprovação não há import: só medição. E o
|
|
16
|
-
* `connect` avisa - em uma linha cinza, a terceira de um bloco secundário. Um portão que interrompe
|
|
17
|
-
* a jornada principal não pode ser anunciado como nota de pé de página.
|
|
18
|
-
*
|
|
19
|
-
* A ORIGEM DO DADO. O Claude Code guarda a decisão no `~/.claude.json`, por projeto:
|
|
20
|
-
*
|
|
21
|
-
* enabledMcpjsonServers aprovados
|
|
22
|
-
* disabledMcpjsonServers recusados
|
|
23
|
-
*
|
|
24
|
-
* Um servidor declarado no `.mcp.json` que não está em nenhuma das duas listas está PENDENTE - e é
|
|
25
|
-
* exatamente o estado dele. Medido: as duas listas vazias, para aquele projeto.
|
|
26
|
-
*
|
|
27
|
-
* SÓ LEITURA, NUNCA ESCRITA. Aquele arquivo é a configuração pessoal dele e vale para todos os
|
|
28
|
-
* projetos dele - a mesma fronteira do `.zshrc`, que este produto só atravessa perguntando.
|
|
29
|
-
* Escrever a aprovação por ele seria decidir em nome dele sobre um portão de segurança.
|
|
30
|
-
*
|
|
31
|
-
* E CALA QUANDO NÃO SABE. Arquivo ausente, JSON que não parseia, projeto sem entrada, formato
|
|
32
|
-
* diferente do que a gente conhece: em todos, resposta `null` e nenhuma linha na tela. Um aviso
|
|
33
|
-
* baseado num formato que mudou é pior que silêncio, porque manda a pessoa procurar um botão que
|
|
34
|
-
* talvez não exista mais.
|
|
35
|
-
*/
|
|
36
|
-
/** O nome do nosso servidor no `.mcp.json` - ver `wireMcpAt`. */
|
|
37
|
-
const SERVER = "synthesisui";
|
|
38
|
-
/**
|
|
39
|
-
* `true` quando o nosso servidor está declarado no repositório e a decisão dele ainda não foi
|
|
40
|
-
* tomada. `null` quando não há como saber - e aí quem chama não diz nada.
|
|
41
|
-
*/
|
|
42
|
-
export async function mcpAwaitingApproval(root, home) {
|
|
43
|
-
const declared = await readFile(join(root, ".mcp.json"), "utf8").then((raw) => {
|
|
44
|
-
try {
|
|
45
|
-
const j = JSON.parse(raw);
|
|
46
|
-
return Boolean(j.mcpServers && SERVER in j.mcpServers);
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}, () => false);
|
|
52
|
-
/** Sem declaração não há aprovação pendente: o caso é OUTRO, e o `connect` já o cobre. */
|
|
53
|
-
if (declared !== true)
|
|
54
|
-
return declared === null ? null : false;
|
|
55
|
-
const raw = await readFile(join(home, ".claude.json"), "utf8").catch(() => null);
|
|
56
|
-
if (raw == null)
|
|
57
|
-
return null;
|
|
58
|
-
let conf;
|
|
59
|
-
try {
|
|
60
|
-
conf = JSON.parse(raw);
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
const entry = conf.projects?.[root];
|
|
66
|
-
/**
|
|
67
|
-
* SEM ENTRADA PARA ESTE PROJETO a pessoa provavelmente nunca abriu o agente aqui - e aí não há
|
|
68
|
-
* aprovação pendente, há uma sessão que ainda não aconteceu. Dizer "aprove" a quem não foi
|
|
69
|
-
* perguntado é inventar um estado.
|
|
70
|
-
*/
|
|
71
|
-
if (!entry)
|
|
72
|
-
return null;
|
|
73
|
-
const enabled = entry.enabledMcpjsonServers;
|
|
74
|
-
const disabled = entry.disabledMcpjsonServers;
|
|
75
|
-
/** Formato diferente do que a gente conhece: silêncio, nunca um palpite. */
|
|
76
|
-
if (!Array.isArray(enabled) || !Array.isArray(disabled))
|
|
77
|
-
return null;
|
|
78
|
-
/** Recusado explicitamente é um estado REAL, e é o único que este arquivo registra com certeza. */
|
|
79
|
-
if (disabled.includes(SERVER))
|
|
80
|
-
return true;
|
|
81
|
-
if (enabled.includes(SERVER))
|
|
82
|
-
return false;
|
|
83
|
-
/**
|
|
84
|
-
* AUSENTE DAS DUAS LISTAS É "NÃO SEI", E TRATÁ-LO COMO "PENDENTE" FOI O DEFEITO.
|
|
85
|
-
*
|
|
86
|
-
* A premissa desta função era: *"um servidor declarado que não está em nenhuma das duas listas
|
|
87
|
-
* está PENDENTE - medido, as duas listas vazias para aquele projeto"*. A medição existiu e estava
|
|
88
|
-
* certa sobre aquele projeto; a INFERÊNCIA não, porque ninguém mediu a outra metade.
|
|
89
|
-
*
|
|
90
|
-
* REMEDIDO em 04/09, na máquina do dono, nos SETE projetos que declaram este servidor: as duas
|
|
91
|
-
* listas estão vazias em TODOS - incluindo o `codelevel-monorepo`, onde o import inteiro rodou no
|
|
92
|
-
* dia anterior com o MCP servindo o playbook. Estado byte a byte idêntico entre "funciona" e
|
|
93
|
-
* "está pendente": a leitura não distinguia nada, era uma constante disfarçada de sinal.
|
|
94
|
-
*
|
|
95
|
-
* E procurei o registro em todas as casas antes de concluir - `~/.claude.json`,
|
|
96
|
-
* `.claude/settings.json`, `.claude/settings.local.json`, `.mcp.json` e `~/.claude/`. A aprovação
|
|
97
|
-
* de um servidor do `.mcp.json` não é gravada em nenhuma delas. `hasTrustDialogAccepted` é o
|
|
98
|
-
* diálogo do PROJETO, e `mcp-needs-auth-cache.json` é dos servidores do claude.ai.
|
|
99
|
-
*
|
|
100
|
-
* O CUSTO DE AFIRMAR SEM SABER, e ele não foi teórico: esta linha chega ao contexto do agente em
|
|
101
|
-
* toda abertura de sessão (`align` no `SessionStart`). Em 04/09 o dono pediu *"import my design
|
|
102
|
-
* system"*, o agente leu o aviso, acreditou nele e RECUSOU a jornada principal - mandou rodar
|
|
103
|
-
* `/mcp` num projeto cujas ferramentas estavam disponíveis. O defeito que o aviso existia para
|
|
104
|
-
* evitar era o agente parar sem saber por quê; o aviso passou a ser a causa dele.
|
|
105
|
-
*
|
|
106
|
-
* Então volta a valer a regra que o cabeçalho deste módulo sempre declarou - *"cala quando não
|
|
107
|
-
* sabe"* -, e quem observa se as ferramentas chegaram é o AGENTE, que sabe quais tem. A instrução
|
|
108
|
-
* mora no playbook, endereçada a ele.
|
|
109
|
-
*/
|
|
110
|
-
return null;
|
|
111
|
-
}
|
package/dist/memory/recall.js
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* QUANTO DA MEMÓRIA MERECE ENTRAR NO CONTEXTO AGORA - e a resposta é explicável linha por linha.
|
|
3
|
-
*
|
|
4
|
-
* ─────────────────────────────────────────────────────────────────────────
|
|
5
|
-
* FILTRO, ORDEM, ORÇAMENTO - nesta ordem, e nenhum score
|
|
6
|
-
*
|
|
7
|
-
* A alternativa considerada e recusada era `relevância × confiança × especificidade`. Ela responde
|
|
8
|
-
* ao mesmo pedido e cobra um preço que só aparece no primeiro dia em que a memória atrapalha: o
|
|
9
|
-
* desenvolvedor pergunta *"por que essa regra apareceu e aquela não"*, e um produto de três floats
|
|
10
|
-
* não tem resposta. Um filtro booleano tem.
|
|
11
|
-
*
|
|
12
|
-
* Confiança responde "quanto acreditamos nisto". Relevância responde "quanto isto importa para o
|
|
13
|
-
* que estou fazendo agora". São perguntas diferentes, e a segunda é um FILTRO de contrato fechado,
|
|
14
|
-
* nunca um peso.
|
|
15
|
-
*
|
|
16
|
-
* ─────────────────────────────────────────────────────────────────────────
|
|
17
|
-
* A INVARIANTE 5 (dono, 20/08): ORÇAMENTO NÃO PODE MUDAR SEMÂNTICA
|
|
18
|
-
*
|
|
19
|
-
* Duas decisões sobre o mesmo assunto formam um GRUPO indivisível. Se só uma couber, ela não entra
|
|
20
|
-
* sozinha - entregar metade de uma discussão como se fosse a visão inteira é reintroduzir
|
|
21
|
-
* contradição silenciosa por causa do orçamento, que é o defeito que o co-escopo existe para
|
|
22
|
-
* impedir. Ou o grupo entra inteiro, ou ele é declarado omitido.
|
|
23
|
-
*
|
|
24
|
-
* ─────────────────────────────────────────────────────────────────────────
|
|
25
|
-
* CO-ESCOPO NÃO É CONTRADIÇÃO, e a diferença é dita em voz alta
|
|
26
|
-
*
|
|
27
|
-
* Duas regras no mesmo `applies` e `kind` não são necessariamente contraditórias: são duas decisões
|
|
28
|
-
* que merecem aparecer juntas. Ver que duas FRASES se contradizem é leitura de linguagem, ou seja
|
|
29
|
-
* IA, e isso está fora do v1 de propósito. O que este arquivo promete é co-escopo, e é isso que a
|
|
30
|
-
* resposta diz - prometer um detector de contradição que não existe seria a lacuna calada que a
|
|
31
|
-
* lei 8 proíbe.
|
|
32
|
-
*/
|
|
33
|
-
/**
|
|
34
|
-
* AS TAREFAS, LISTA FECHADA - derivada do que a plataforma JÁ MEDE, nunca de casos imaginados.
|
|
35
|
-
*
|
|
36
|
-
* Mesma regra do catálogo de predicados: a linguagem cresce quando a capacidade de medição cresce.
|
|
37
|
-
* Estas seis existem porque há medição por trás de cada uma - as três categorias de otimização, o
|
|
38
|
-
* contraste que o doctor calcula, a acessibilidade que os anéis medem, e a migração, que é o que os
|
|
39
|
-
* predicados de transição observam.
|
|
40
|
-
*/
|
|
41
|
-
export const TASKS = [
|
|
42
|
-
"tokens",
|
|
43
|
-
"spacing",
|
|
44
|
-
"typography",
|
|
45
|
-
"contrast",
|
|
46
|
-
"accessibility",
|
|
47
|
-
"migration",
|
|
48
|
-
];
|
|
49
|
-
/** ~4 caracteres por token: aproximação suficiente para um teto, e barata. */
|
|
50
|
-
const cost = (m) => Math.ceil(m.text.length / 4) + 8;
|
|
51
|
-
const norm = (v) => v.trim().toLowerCase();
|
|
52
|
-
/**
|
|
53
|
-
* A STACK É O ÚNICO FILTRO QUE MANTÉM O QUE NÃO CONSEGUE JULGAR.
|
|
54
|
-
*
|
|
55
|
-
* Uma regra cuja condição não bate é removida, porque obedecê-la seria um erro. Uma que a gente não
|
|
56
|
-
* consegue avaliar - a stack veio vazia, ou ela nomeia algo que não detectamos - FICA, porque uma
|
|
57
|
-
* regra que ninguém pode julgar é mais segura presente que silenciosamente ausente. São duas
|
|
58
|
-
* situações diferentes e elas ganham respostas diferentes.
|
|
59
|
-
*/
|
|
60
|
-
const stackHolds = (m, stack) => {
|
|
61
|
-
if (m.when.length === 0)
|
|
62
|
-
return true;
|
|
63
|
-
if (!stack || stack.length === 0)
|
|
64
|
-
return true;
|
|
65
|
-
const has = new Set(stack.map(norm));
|
|
66
|
-
return m.when.some((w) => has.has(norm(w)));
|
|
67
|
-
};
|
|
68
|
-
/** Especificidade: a relação vence o componente, que vence o sistema - e sai do dado, sem peso. */
|
|
69
|
-
const specificity = (m) => m.applies.length >= 2 ? 3 : m.applies.length === 1 ? 2 : 1;
|
|
70
|
-
const RUNG_ORDER = { fact: 0, habit: 1, candidate: 2 };
|
|
71
|
-
/**
|
|
72
|
-
* A CHAVE DO GRUPO: o sujeito estruturado quando existe, senão o escopo mais o tipo de regra.
|
|
73
|
-
*
|
|
74
|
-
* Sem sujeito o agrupamento é mais largo e pode juntar coisas que só dividem o escopo - e é por
|
|
75
|
-
* isso que a resposta diz "há mais de uma decisão neste escopo" em vez de afirmar conflito.
|
|
76
|
-
*/
|
|
77
|
-
const groupKey = (m) => m.subject
|
|
78
|
-
? `subject:${norm(m.subject)}`
|
|
79
|
-
: `scope:${m.applies.map(norm).sort().join("+")}|${m.kind}`;
|
|
80
|
-
export function recall(all, ask) {
|
|
81
|
-
const omitted = new Map();
|
|
82
|
-
const drop = (why) => omitted.set(why, (omitted.get(why) ?? 0) + 1);
|
|
83
|
-
const wantScope = (ask.scope ?? []).map(norm);
|
|
84
|
-
const kept = all.filter((m) => {
|
|
85
|
-
if (!stackHolds(m, ask.stack))
|
|
86
|
-
return drop("stack"), false;
|
|
87
|
-
if (ask.family && m.family && norm(m.family) !== norm(ask.family))
|
|
88
|
-
return drop("family"), false;
|
|
89
|
-
if (ask.task &&
|
|
90
|
-
m.tasks &&
|
|
91
|
-
m.tasks.length > 0 &&
|
|
92
|
-
!m.tasks.includes(ask.task))
|
|
93
|
-
return drop("task"), false;
|
|
94
|
-
/**
|
|
95
|
-
* ESCOPO: uma regra DO SISTEMA vale sempre - ela é o que governa tudo. Uma regra de componente
|
|
96
|
-
* só entra quando aquele componente está em jogo.
|
|
97
|
-
*/
|
|
98
|
-
if (m.applies.length > 0 && wantScope.length > 0) {
|
|
99
|
-
const touches = m.applies.some((a) => wantScope.includes(norm(a)));
|
|
100
|
-
if (!touches)
|
|
101
|
-
return drop("scope"), false;
|
|
102
|
-
}
|
|
103
|
-
return true;
|
|
104
|
-
});
|
|
105
|
-
/** Ordem: mais específico, depois degrau, depois confiança, e decisão antes de rationale. */
|
|
106
|
-
const ranked = [...kept].sort((a, b) => {
|
|
107
|
-
if (a.type !== b.type)
|
|
108
|
-
return a.type === "decision" ? -1 : 1;
|
|
109
|
-
const s = specificity(b) - specificity(a);
|
|
110
|
-
if (s !== 0)
|
|
111
|
-
return s;
|
|
112
|
-
const r = RUNG_ORDER[a.rung] - RUNG_ORDER[b.rung];
|
|
113
|
-
if (r !== 0)
|
|
114
|
-
return r;
|
|
115
|
-
return b.confidence - a.confidence;
|
|
116
|
-
});
|
|
117
|
-
/**
|
|
118
|
-
* O ORÇAMENTO É GASTO POR GRUPO, nunca por item - é a invariante 5.
|
|
119
|
-
*
|
|
120
|
-
* Os grupos entram na ordem do seu melhor membro, e um grupo que não cabe INTEIRO não entra:
|
|
121
|
-
* metade de uma discussão lida como a visão completa.
|
|
122
|
-
*/
|
|
123
|
-
const order = [];
|
|
124
|
-
const byGroup = new Map();
|
|
125
|
-
for (const m of ranked) {
|
|
126
|
-
const key = groupKey(m);
|
|
127
|
-
if (!byGroup.has(key)) {
|
|
128
|
-
byGroup.set(key, []);
|
|
129
|
-
order.push(key);
|
|
130
|
-
}
|
|
131
|
-
byGroup.get(key)?.push(m);
|
|
132
|
-
}
|
|
133
|
-
const carried = [];
|
|
134
|
-
const groups = [];
|
|
135
|
-
let spent = 0;
|
|
136
|
-
for (const key of order) {
|
|
137
|
-
const members = byGroup.get(key) ?? [];
|
|
138
|
-
const price = members.reduce((n, m) => n + cost(m), 0);
|
|
139
|
-
if (spent + price > ask.budget) {
|
|
140
|
-
for (const _ of members)
|
|
141
|
-
drop("budget");
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
spent += price;
|
|
145
|
-
carried.push(...members);
|
|
146
|
-
if (members.length > 1)
|
|
147
|
-
groups.push({ key, members: members.map((m) => m.id) });
|
|
148
|
-
}
|
|
149
|
-
const cut = [...omitted.entries()].map(([by, count]) => ({ by, count }));
|
|
150
|
-
const total = all.length;
|
|
151
|
-
const parts = cut.map(({ by, count }) => `${count} by ${by}`).join(", ");
|
|
152
|
-
const conflictNote = groups.length > 0
|
|
153
|
-
? ` ${groups.length} group${groups.length === 1 ? "" : "s"} carry more than one decision on the same subject - they travel together, and this is co-scope, not a claim that they contradict.`
|
|
154
|
-
: "";
|
|
155
|
-
return {
|
|
156
|
-
carried,
|
|
157
|
-
groups,
|
|
158
|
-
omitted: cut,
|
|
159
|
-
because: `${carried.length} of ${total} carried within ${ask.budget} tokens${parts ? `; left out: ${parts}` : ""}.${conflictNote}`,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/** Dias distintos com medição, sem progresso, antes de o estado adormecer. */
|
|
2
|
-
export const DORMANT_AFTER_MEASURED_DAYS = 3;
|
|
3
|
-
const day = (iso) => iso.slice(0, 10);
|
|
4
|
-
/** Estados que uma medição não move mais. Uma regressão é um trabalho NOVO, não este voltando. */
|
|
5
|
-
const SETTLED = new Set(["completed", "abandoned"]);
|
|
6
|
-
/**
|
|
7
|
-
* O QUE A MEDIÇÃO FAZ COM UM ESTADO - a única porta por onde `completed` e `orphaned` entram.
|
|
8
|
-
*
|
|
9
|
-
* PROGRESSO É O VEREDITO TER MUDADO, e isso é determinístico sem guardar o repositório inteiro: o
|
|
10
|
-
* `because` de um predicado de transição sai de "still references X" para "dropped X but does not
|
|
11
|
-
* reference Y yet" quando metade do trabalho aconteceu. Comparar o veredito anterior com o atual
|
|
12
|
-
* captura isso sem inventar heurística de diff.
|
|
13
|
-
*/
|
|
14
|
-
export function onMeasurement(state, verdict, at, by, dormantAfter = DORMANT_AFTER_MEASURED_DAYS) {
|
|
15
|
-
if (SETTLED.has(state.status))
|
|
16
|
-
return state;
|
|
17
|
-
/**
|
|
18
|
-
* INVARIANTE 1 e 2, no mesmo lugar: indeterminado NÃO PODE concluir, e ficar órfão preserva
|
|
19
|
-
* tudo. `lastVerdict` não é sobrescrito - a última coisa que a gente soube de verdade sobrevive
|
|
20
|
-
* ao período em que a gente deixou de saber.
|
|
21
|
-
*/
|
|
22
|
-
if (verdict.state === "indeterminate")
|
|
23
|
-
return {
|
|
24
|
-
...state,
|
|
25
|
-
status: "orphaned",
|
|
26
|
-
lastMeasuredAt: at,
|
|
27
|
-
by: by ?? state.by,
|
|
28
|
-
resolution: {
|
|
29
|
-
type: "orphaned",
|
|
30
|
-
source: "measurement",
|
|
31
|
-
at,
|
|
32
|
-
reason: `${verdict.reason}: ${verdict.because}`,
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
const progressed = !state.lastVerdict ||
|
|
36
|
-
state.lastVerdict.state !== verdict.state ||
|
|
37
|
-
state.lastVerdict.because !== verdict.because;
|
|
38
|
-
if (verdict.state === "met")
|
|
39
|
-
return {
|
|
40
|
-
...state,
|
|
41
|
-
status: "completed",
|
|
42
|
-
evidenceSource: "measurement",
|
|
43
|
-
lastMeasuredAt: at,
|
|
44
|
-
lastProgressAt: at,
|
|
45
|
-
measuredDays: [],
|
|
46
|
-
lastVerdict: { state: "met", because: verdict.because },
|
|
47
|
-
by: by ?? state.by,
|
|
48
|
-
resolution: {
|
|
49
|
-
type: "completed",
|
|
50
|
-
source: "measurement",
|
|
51
|
-
at,
|
|
52
|
-
reason: verdict.because,
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
/**
|
|
56
|
-
* INVARIANTE 3: uma medição nova NÃO acorda um estado dormente. Só progresso acorda - senão
|
|
57
|
-
* rodar `sync` faria todo trabalho parado parecer vivo outra vez.
|
|
58
|
-
*/
|
|
59
|
-
if (progressed)
|
|
60
|
-
return {
|
|
61
|
-
...state,
|
|
62
|
-
status: "active",
|
|
63
|
-
evidenceSource: "measurement",
|
|
64
|
-
lastMeasuredAt: at,
|
|
65
|
-
lastProgressAt: at,
|
|
66
|
-
measuredDays: [],
|
|
67
|
-
lastVerdict: { state: "unmet", because: verdict.because },
|
|
68
|
-
by: by ?? state.by,
|
|
69
|
-
/** Um estado que estava órfão e voltou a ser mensurável perde a resolução antiga. */
|
|
70
|
-
...(state.status === "orphaned" ? { resolution: undefined } : {}),
|
|
71
|
-
};
|
|
72
|
-
const days = state.measuredDays.includes(day(at))
|
|
73
|
-
? state.measuredDays
|
|
74
|
-
: [...state.measuredDays, day(at)];
|
|
75
|
-
return {
|
|
76
|
-
...state,
|
|
77
|
-
status: days.length >= dormantAfter
|
|
78
|
-
? "dormant"
|
|
79
|
-
: state.status === "orphaned"
|
|
80
|
-
? "active"
|
|
81
|
-
: state.status,
|
|
82
|
-
evidenceSource: "measurement",
|
|
83
|
-
lastMeasuredAt: at,
|
|
84
|
-
measuredDays: days,
|
|
85
|
-
lastVerdict: { state: "unmet", because: verdict.because },
|
|
86
|
-
by: by ?? state.by,
|
|
87
|
-
...(state.status === "orphaned" ? { resolution: undefined } : {}),
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
/** A ÚNICA porta para `abandoned`, e ela é uma pessoa. */
|
|
91
|
-
export function declareAbandoned(state, by, at, reason) {
|
|
92
|
-
return {
|
|
93
|
-
...state,
|
|
94
|
-
status: "abandoned",
|
|
95
|
-
resolution: {
|
|
96
|
-
type: "abandoned",
|
|
97
|
-
source: "developer",
|
|
98
|
-
at,
|
|
99
|
-
...(reason ? { reason } : {}),
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* COMO O RECALL FALA DE UM ESTADO - invariante 4.
|
|
105
|
-
*
|
|
106
|
-
* Um estado que o agente criou e que ninguém mediu é hipótese, e a frase tem que dizer isso. Um
|
|
107
|
-
* estado medido há seis dias é a última coisa que a gente sabe, não o que está acontecendo agora.
|
|
108
|
-
*/
|
|
109
|
-
export function authorityOf(state) {
|
|
110
|
-
if (state.createdBy === "agent" && state.evidenceSource === "none")
|
|
111
|
-
return {
|
|
112
|
-
authority: "hypothesis",
|
|
113
|
-
label: "hypothesis, written by an agent and never measured",
|
|
114
|
-
};
|
|
115
|
-
if (!state.lastMeasuredAt)
|
|
116
|
-
return { authority: "hypothesis", label: "declared, not measured yet" };
|
|
117
|
-
if (state.status === "orphaned")
|
|
118
|
-
return {
|
|
119
|
-
authority: "last-known",
|
|
120
|
-
label: `last known before it became unmeasurable on ${day(state.lastMeasuredAt)}`,
|
|
121
|
-
};
|
|
122
|
-
if (state.status === "dormant")
|
|
123
|
-
return {
|
|
124
|
-
authority: "last-known",
|
|
125
|
-
label: `no progress observed since ${state.lastProgressAt ? day(state.lastProgressAt) : day(state.createdAt)}`,
|
|
126
|
-
};
|
|
127
|
-
return {
|
|
128
|
-
authority: "confirmed",
|
|
129
|
-
label: `measured ${day(state.lastMeasuredAt)}`,
|
|
130
|
-
};
|
|
131
|
-
}
|