dd-harness-mcp 0.13.0 → 0.15.0
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/cli/src/cinto.js +45 -4
- package/dist/cli/src/curar.js +49 -0
- package/dist/cli/src/gravar.js +13 -0
- package/dist/cli/src/index.js +107 -6
- package/dist/cli/src/init.js +23 -5
- package/dist/mcp/src/index.js +75 -3
- package/package.json +1 -1
package/dist/cli/src/cinto.js
CHANGED
|
@@ -25,19 +25,55 @@ import { memoriasNoConteudo } from "./diff.js";
|
|
|
25
25
|
export function caminhoDoCache(raiz) {
|
|
26
26
|
return join(raiz, ".claude", "dd-harness-ancoras.json");
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Acrescenta UMA memoria ao cache, sem esperar a proxima sessao.
|
|
30
|
+
*
|
|
31
|
+
* Existe por um furo medido na rodada 006: o cache so era escrito pelo `SessionStart`, e
|
|
32
|
+
* memoria gravada no meio da sessao ficava invisivel ao cinto ate a sessao seguinte —
|
|
33
|
+
* justo no fluxo mais natural, que e gravar a memoria e ser avisado minutos depois se
|
|
34
|
+
* alguem tentar desfazer o que ela protege. A memoria nascia sem proteger nada.
|
|
35
|
+
*
|
|
36
|
+
* Escreve so o que ja esta na mao de quem gravou: sem rede, porque nao ha payload novo a
|
|
37
|
+
* buscar, e uma requisicao a mais no caminho de escrita nao se justifica para atualizar
|
|
38
|
+
* um cache que a proxima sessao reconstroi inteiro de qualquer jeito.
|
|
39
|
+
*
|
|
40
|
+
* Idempotente pelo endereco: regravar a mesma memoria substitui a entrada, nunca duplica.
|
|
41
|
+
*/
|
|
42
|
+
export async function acrescentaAoCache(raiz, memoria) {
|
|
43
|
+
if (memoria.ancoras.length === 0)
|
|
44
|
+
return;
|
|
45
|
+
const atual = (await leCache(raiz)) ?? { gravado_em: "", memorias: [] };
|
|
46
|
+
const endereco = `${memoria.pasta}/${memoria.slug}`;
|
|
47
|
+
const cache = {
|
|
48
|
+
gravado_em: new Date().toISOString(),
|
|
49
|
+
memorias: [
|
|
50
|
+
...atual.memorias.filter((m) => `${m.pasta}/${m.slug}` !== endereco),
|
|
51
|
+
memoria,
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
try {
|
|
55
|
+
const caminho = caminhoDoCache(raiz);
|
|
56
|
+
await mkdir(dirname(caminho), { recursive: true });
|
|
57
|
+
await writeFile(caminho, `${JSON.stringify(cache)}\n`, "utf8");
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Mesmo motivo do `guardaCache`: cache e otimizacao, nao contrato.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
28
63
|
/** Guarda as ancoras do Brain para o hook consultar sem rede. Falha em silencio: cache e otimizacao, nao contrato. */
|
|
29
64
|
export async function guardaCache(raiz, brain) {
|
|
30
65
|
const cache = {
|
|
31
66
|
gravado_em: new Date().toISOString(),
|
|
32
67
|
memorias: brain.memorias
|
|
33
68
|
.filter((m) => m.status === "ativa" && m.ancoras.length > 0)
|
|
34
|
-
.map(({ pasta, slug, titulo, resumo, status, ancoras }) => ({
|
|
69
|
+
.map(({ pasta, slug, titulo, resumo, status, ancoras, escopo }) => ({
|
|
35
70
|
pasta,
|
|
36
71
|
slug,
|
|
37
72
|
titulo,
|
|
38
73
|
resumo,
|
|
39
74
|
status,
|
|
40
75
|
ancoras,
|
|
76
|
+
escopo,
|
|
41
77
|
})),
|
|
42
78
|
};
|
|
43
79
|
try {
|
|
@@ -108,7 +144,7 @@ export function caminhoRelativo(raiz, arquivo) {
|
|
|
108
144
|
* Vazio e o caso comum e tem que sair barato: a maioria das edicoes nao toca ancora
|
|
109
145
|
* alguma, e o silencio ai nao e falta de aviso, e a ausencia de motivo para avisar.
|
|
110
146
|
*/
|
|
111
|
-
export function alerta(tocadas, resumos) {
|
|
147
|
+
export function alerta(tocadas, resumos, globais = new Set()) {
|
|
112
148
|
if (tocadas.length === 0)
|
|
113
149
|
return "";
|
|
114
150
|
const linhas = [
|
|
@@ -117,7 +153,11 @@ export function alerta(tocadas, resumos) {
|
|
|
117
153
|
];
|
|
118
154
|
for (const t of tocadas) {
|
|
119
155
|
const endereco = `${t.pasta}/${t.memoria}`;
|
|
120
|
-
|
|
156
|
+
// Marcar a global muda o peso do que se le: a licao nao fala DESTE projeto, fala de
|
|
157
|
+
// todos. Sem a marca, o agente a avalia como decisao local e pode concluir que "aqui
|
|
158
|
+
// e diferente" — que e exatamente o raciocinio que a promocao existiu para vencer.
|
|
159
|
+
const marca = globais.has(endereco) ? " · MEMÓRIA GLOBAL" : "";
|
|
160
|
+
linhas.push(`## ${t.titulo}${marca}`, `\`${endereco}\` — âncora: \`${t.ancora}\``, "");
|
|
121
161
|
const resumo = resumos.get(endereco);
|
|
122
162
|
if (resumo)
|
|
123
163
|
linhas.push(resumo, "");
|
|
@@ -168,11 +208,12 @@ export async function decideDoHook(entrada) {
|
|
|
168
208
|
if (novas.length === 0)
|
|
169
209
|
return permitir;
|
|
170
210
|
const resumos = new Map(cache.memorias.map((m) => [`${m.pasta}/${m.slug}`, m.resumo]));
|
|
211
|
+
const globais = new Set(cache.memorias.filter((m) => m.escopo === "global").map((m) => `${m.pasta}/${m.slug}`));
|
|
171
212
|
return JSON.stringify({
|
|
172
213
|
hookSpecificOutput: {
|
|
173
214
|
hookEventName: "PreToolUse",
|
|
174
215
|
permissionDecision: "allow",
|
|
175
|
-
additionalContext: alerta(novas, resumos),
|
|
216
|
+
additionalContext: alerta(novas, resumos, globais),
|
|
176
217
|
},
|
|
177
218
|
});
|
|
178
219
|
}
|
package/dist/cli/src/curar.js
CHANGED
|
@@ -92,3 +92,52 @@ export async function arquiva(raiz, endereco, opcoes) {
|
|
|
92
92
|
const lido = (await resposta.json());
|
|
93
93
|
return lido;
|
|
94
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Promove a memoria a global — ou a traz de volta ao projeto de origem.
|
|
97
|
+
*
|
|
98
|
+
* Global vale para TODO projeto do espaco, inclusive os que ainda nao existem. E o degrau
|
|
99
|
+
* acima de `memory_projects`, que lista projetos nomeados: aqui o alcance deixa de ser
|
|
100
|
+
* uma lista e vira uma propriedade.
|
|
101
|
+
*/
|
|
102
|
+
export async function promove(raiz, endereco, global) {
|
|
103
|
+
const { config, token } = await credencial(raiz);
|
|
104
|
+
const resposta = await pede(`${config.api}/api/v1/memorias/${endereco}/escopo`, {
|
|
105
|
+
method: "PUT",
|
|
106
|
+
headers: cabecalhos(token, true),
|
|
107
|
+
body: JSON.stringify({ tenant: config.tenant, projeto: config.projeto, global }),
|
|
108
|
+
});
|
|
109
|
+
if (!resposta.ok)
|
|
110
|
+
await recusa(resposta);
|
|
111
|
+
return (await resposta.json());
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Apaga de verdade, em cascata — ancoras, deriva medida, vinculos, tudo.
|
|
115
|
+
*
|
|
116
|
+
* Diferente de `arquiva`, que e o caminho normal: arquivar guarda o conteudo porque o que
|
|
117
|
+
* a memoria dizia pode voltar a importar. Isto e para o que nunca deveria ter existido.
|
|
118
|
+
*
|
|
119
|
+
* Sem `confirmacao`, o servidor RECUSA e devolve o que a cascata levaria junto — e so
|
|
120
|
+
* entao se repete a chamada com o nome do espaco. Duas etapas de proposito: DELETE nao
|
|
121
|
+
* tem desfazer, e a cascata e invisivel de fora.
|
|
122
|
+
*/
|
|
123
|
+
export async function apaga(raiz, endereco, confirmacao) {
|
|
124
|
+
const { config, token } = await credencial(raiz);
|
|
125
|
+
const resposta = await pede(`${config.api}/api/v1/memorias/${endereco}/apagar`, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
headers: cabecalhos(token, true),
|
|
128
|
+
body: JSON.stringify({
|
|
129
|
+
tenant: config.tenant,
|
|
130
|
+
...(confirmacao ? { confirmacao } : {}),
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
// 409 aqui nao e falha: e "ainda nao" — falta confirmar, ou ha uma arquivada apontando
|
|
134
|
+
// para esta. O texto do servidor e a resposta, entao ele passa adiante em vez de virar
|
|
135
|
+
// excecao com mensagem generica.
|
|
136
|
+
if (resposta.status === 409) {
|
|
137
|
+
const lido = (await resposta.json());
|
|
138
|
+
return { apagou: false, detalhe: lido.erro, confirmacaoEsperada: lido.confirmacao_esperada };
|
|
139
|
+
}
|
|
140
|
+
if (!resposta.ok)
|
|
141
|
+
await recusa(resposta);
|
|
142
|
+
return (await resposta.json());
|
|
143
|
+
}
|
package/dist/cli/src/gravar.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { recusaPorAcentuacao } from "./acentuacao.js";
|
|
3
3
|
import { cabecalhos, credencial, pede, recusa } from "./api.js";
|
|
4
|
+
import { acrescentaAoCache } from "./cinto.js";
|
|
4
5
|
const OBRIGATORIOS = ["name", "titulo", "description", "pasta"];
|
|
5
6
|
/**
|
|
6
7
|
* Frontmatter simples: `chave: valor` por linha. Sem lib de YAML — nao ha aninhamento.
|
|
@@ -188,6 +189,18 @@ export async function grava(raiz, caminho) {
|
|
|
188
189
|
if (!resposta.ok)
|
|
189
190
|
await recusa(resposta);
|
|
190
191
|
const { endereco } = (await resposta.json());
|
|
192
|
+
// O cinto passa a enxergar esta memoria AGORA, nao na proxima sessao. Sem isto, a
|
|
193
|
+
// memoria recem-gravada nao protegia nada ate a sessao seguinte — medido na rodada 006,
|
|
194
|
+
// e justamente no fluxo mais comum: gravar a decisao e, minutos depois, alguem tentar
|
|
195
|
+
// desfazer o que ela guarda.
|
|
196
|
+
await acrescentaAoCache(raiz, {
|
|
197
|
+
pasta: memoria.pasta,
|
|
198
|
+
slug: memoria.slug,
|
|
199
|
+
titulo: memoria.titulo,
|
|
200
|
+
resumo: memoria.resumo,
|
|
201
|
+
status: "ativa",
|
|
202
|
+
ancoras: memoria.ancoras.map((valor) => ({ tipo: "caminho", valor, sha: null })),
|
|
203
|
+
});
|
|
191
204
|
return {
|
|
192
205
|
endereco,
|
|
193
206
|
ancoras: memoria.ancoras.length,
|
package/dist/cli/src/index.js
CHANGED
|
@@ -22,7 +22,7 @@ import { buscaPolitica } from "./politica.js";
|
|
|
22
22
|
import { avisoDeOrdem, blocoDeSessao, criaFase, editaFase, formataChangelog, formataRoadmap, leFases, } from "./roadmap.js";
|
|
23
23
|
import { decideDoHook, guardaCache } from "./cinto.js";
|
|
24
24
|
import { busca } from "./buscar.js";
|
|
25
|
-
import { arquiva, edita, le } from "./curar.js";
|
|
25
|
+
import { apaga, arquiva, edita, le, promove } from "./curar.js";
|
|
26
26
|
import { criaPasta } from "./pasta.js";
|
|
27
27
|
import { criaProjeto, listaTenants } from "./projeto.js";
|
|
28
28
|
import { reancora } from "./reancorar.js";
|
|
@@ -52,6 +52,15 @@ const AJUDA = `dd-harness — a política e o Brain do projeto, no serviço
|
|
|
52
52
|
dd-harness arquivar <pasta>/<slug> --motivo <obsoleta|incorreta|fora_dos_filtros>
|
|
53
53
|
[--substituida-por <pasta>/<slug>]
|
|
54
54
|
tira de circulação sem apagar
|
|
55
|
+
dd-harness promover <pasta>/<slug>
|
|
56
|
+
torna a memória global: vale para TODO
|
|
57
|
+
projeto do espaço, inclusive os futuros
|
|
58
|
+
dd-harness despromover <pasta>/<slug>
|
|
59
|
+
traz de volta ao alcance dos vínculos
|
|
60
|
+
dd-harness apagar <pasta>/<slug> [--confirmar <espaço>]
|
|
61
|
+
apaga de vez, em cascata — sem desfazer.
|
|
62
|
+
Para tirar de circulação guardando o
|
|
63
|
+
conteúdo, use "arquivar"
|
|
55
64
|
dd-harness buscar "<pergunta>" acha memória por relevância, não por arquivo
|
|
56
65
|
dd-harness ler <pasta>/<slug> imprime a memória inteira, no formato de gravar
|
|
57
66
|
dd-harness reancorar <pasta>/<slug> --de "<alvo>" --para "<alvo>"
|
|
@@ -180,6 +189,10 @@ async function comandoFase(argv) {
|
|
|
180
189
|
* primeira conexao do Claude Code. Sem isto, a primeira instalacao acontecia so quando
|
|
181
190
|
* o host tentava conectar ao servidor MCP, e o handshake tem timeout curto demais para
|
|
182
191
|
* esperar o download: a sessao real via `CONNECTION_CLOSED` em vez de "instalando".
|
|
192
|
+
*
|
|
193
|
+
* Roda em TODO `start`, nao so no primeiro: e tambem o ponto em que o cache do npx e
|
|
194
|
+
* renovado para a versao mais recente. Um projeto ja configurado que roda `start` de novo
|
|
195
|
+
* sai dali com CLI e MCP frescos, sem ninguem precisar lembrar de atualizar nada.
|
|
183
196
|
*/
|
|
184
197
|
function instalaMcp() {
|
|
185
198
|
return new Promise((resolve) => {
|
|
@@ -187,7 +200,14 @@ function instalaMcp() {
|
|
|
187
200
|
// segundos) e, se falhar, ve POR QUE — em vez do "nao consegui" mudo que nao dava
|
|
188
201
|
// pista nenhuma para diagnosticar. O DEP0190 que isto dispara e suprimido no topo
|
|
189
202
|
// do arquivo (`process.removeAllListeners("warning")`).
|
|
190
|
-
|
|
203
|
+
//
|
|
204
|
+
// `@latest` EXPLICITO, e nao `dd-harness-mcp` pelado: sem a tag, o npx serve a copia
|
|
205
|
+
// que ja estiver no cache dele e nem consulta o registro. Medido na rodada 006 — a
|
|
206
|
+
// cobaia rodou uma sessao inteira contra um MCP defasado sem nada denunciar. O
|
|
207
|
+
// `.mcp.json` continua declarando o pacote SEM versao, de proposito: o que precisa
|
|
208
|
+
// estar fresco e o cache, e fixar versao no arquivo obrigaria a reescrever config em
|
|
209
|
+
// todo repositorio a cada publicacao.
|
|
210
|
+
const p = spawn("npx", ["-y", "dd-harness-mcp@latest", "--help"], {
|
|
191
211
|
shell: process.platform === "win32",
|
|
192
212
|
stdio: "inherit",
|
|
193
213
|
});
|
|
@@ -221,6 +241,32 @@ async function versaoDesatualizada() {
|
|
|
221
241
|
return null;
|
|
222
242
|
}
|
|
223
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Instala a versao mais nova do CLI globalmente, de dentro do proprio wizard.
|
|
246
|
+
*
|
|
247
|
+
* Ate aqui o `start` so AVISAVA e mandava o usuario rodar `npm i -g` e recomecar — e
|
|
248
|
+
* "comece de novo" no meio de um wizard e o tipo de instrucao que ninguem segue. A
|
|
249
|
+
* rodada 006 mostrou o custo: a sessao inteira rodou contra um CLI defasado, com o
|
|
250
|
+
* relatorio registrando "ja instalado, nao atualizado" como se fosse normal.
|
|
251
|
+
*
|
|
252
|
+
* O binario EM EXECUCAO continua sendo o antigo ate este processo terminar — nao ha como
|
|
253
|
+
* trocar codigo de baixo de si mesmo. Isso e seguro aqui porque o que resta do wizard so
|
|
254
|
+
* escreve configuracao (JSON e markdown), nada que dependa de correcao nova; e do
|
|
255
|
+
* proximo comando em diante tudo ja e a versao nova.
|
|
256
|
+
*
|
|
257
|
+
* Nunca lanca: falhar em atualizar nao pode travar o `start`. Sem permissao para escrever
|
|
258
|
+
* no diretorio global (o caso mais comum), o wizard segue e o aviso continua valendo.
|
|
259
|
+
*/
|
|
260
|
+
function atualizaCli(ultima) {
|
|
261
|
+
return new Promise((resolve) => {
|
|
262
|
+
const p = spawn("npm", ["install", "-g", `dd-harness@${ultima}`], {
|
|
263
|
+
shell: process.platform === "win32",
|
|
264
|
+
stdio: "inherit",
|
|
265
|
+
});
|
|
266
|
+
p.on("exit", (code) => resolve(code === 0));
|
|
267
|
+
p.on("error", () => resolve(false));
|
|
268
|
+
});
|
|
269
|
+
}
|
|
224
270
|
/**
|
|
225
271
|
* Tenta abrir o navegador padrao. Nunca lanca — falhar em abrir nao pode travar o
|
|
226
272
|
* wizard, so degrada para "aqui esta a URL, abra voce mesmo".
|
|
@@ -271,9 +317,14 @@ async function comandoStart() {
|
|
|
271
317
|
// pre-instalacao do MCP, passo 5.5 abaixo, numa versao anterior a ela existir).
|
|
272
318
|
const desatualizado = await versaoDesatualizada();
|
|
273
319
|
if (desatualizado) {
|
|
274
|
-
console.log(`
|
|
275
|
-
|
|
276
|
-
|
|
320
|
+
console.log(`Atualizando o dd-harness: ${desatualizado.atual} → ${desatualizado.ultima}...`);
|
|
321
|
+
const atualizou = await atualizaCli(desatualizado.ultima);
|
|
322
|
+
console.log(atualizou
|
|
323
|
+
? `Pronto — ${desatualizado.ultima} instalada. O wizard segue na ${desatualizado.atual} ` +
|
|
324
|
+
"(o binário em execução não troca sozinho); do próximo comando em diante, a nova.\n"
|
|
325
|
+
: `Não consegui atualizar — o npm explicou o motivo acima. Você segue na ` +
|
|
326
|
+
`${desatualizado.atual}, e a mais recente é ${desatualizado.ultima}: rode ` +
|
|
327
|
+
"`npm install -g dd-harness@latest` quando puder. O start continua assim mesmo.\n");
|
|
277
328
|
}
|
|
278
329
|
// 1. Credencial. Sem ela nada do resto e possivel — nem listar tenant.
|
|
279
330
|
// O estado aparece SEMPRE, mesmo quando ja existe credencial: antes o passo era
|
|
@@ -366,7 +417,8 @@ async function comandoStart() {
|
|
|
366
417
|
}[mcp.estado]);
|
|
367
418
|
}
|
|
368
419
|
if (mcp.ok) {
|
|
369
|
-
console.log("\nInstalando
|
|
420
|
+
console.log("\nInstalando a versão mais recente do servidor dd-harness-mcp " +
|
|
421
|
+
"(a primeira vez pode demorar um pouco)...");
|
|
370
422
|
const { ok: instalou, erro: erroInstalacao } = await instalaMcp();
|
|
371
423
|
console.log(instalou
|
|
372
424
|
? "dd-harness-mcp instalado — a próxima sessão do Claude Code conecta na hora."
|
|
@@ -477,6 +529,49 @@ async function comandoEditar(argv) {
|
|
|
477
529
|
console.log(` ${r.ancoras} âncora(s).`);
|
|
478
530
|
}
|
|
479
531
|
const MOTIVOS = ["obsoleta", "incorreta", "fora_dos_filtros"];
|
|
532
|
+
/**
|
|
533
|
+
* `promover` / `despromover` — o alcance da memoria.
|
|
534
|
+
*
|
|
535
|
+
* Global vale para TODO projeto do espaco, inclusive os que ainda nao existem. Nao e o
|
|
536
|
+
* mesmo que `projetos:` no frontmatter, que lista projetos nomeados: la o alcance e uma
|
|
537
|
+
* lista, aqui e uma propriedade.
|
|
538
|
+
*/
|
|
539
|
+
async function comandoPromover(argv, global) {
|
|
540
|
+
const endereco = argv[0];
|
|
541
|
+
const verbo = global ? "promover" : "despromover";
|
|
542
|
+
if (!endereco || endereco.startsWith("-") || !endereco.includes("/")) {
|
|
543
|
+
throw new Error(`uso: dd-harness ${verbo} <pasta>/<slug>`);
|
|
544
|
+
}
|
|
545
|
+
const r = await promove(process.cwd(), endereco, global);
|
|
546
|
+
console.log(global
|
|
547
|
+
? `${endereco} agora é GLOBAL — vale para todo projeto de ${r.tenant}, inclusive os que ainda não existem.`
|
|
548
|
+
: `${endereco} voltou a valer só para os projetos a que está vinculada.`);
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* `apagar` — o unico caminho que destroi.
|
|
552
|
+
*
|
|
553
|
+
* Duas etapas, e a segunda pede o nome do espaco digitado. Nao e cerimonia: a cascata leva
|
|
554
|
+
* ancoras e toda a deriva medida delas, e nao ha desfazer. Arquivar continua sendo o
|
|
555
|
+
* caminho normal — isto e para o que nunca deveria ter existido.
|
|
556
|
+
*/
|
|
557
|
+
async function comandoApagar(argv) {
|
|
558
|
+
const endereco = argv[0];
|
|
559
|
+
if (!endereco || endereco.startsWith("-") || !endereco.includes("/")) {
|
|
560
|
+
throw new Error("uso: dd-harness apagar <pasta>/<slug> [--confirmar <espaço>]");
|
|
561
|
+
}
|
|
562
|
+
const r = await apaga(process.cwd(), endereco, argumento(argv, "confirmar"));
|
|
563
|
+
if (!r.apagou) {
|
|
564
|
+
console.log(r.detalhe);
|
|
565
|
+
if (r.confirmacaoEsperada) {
|
|
566
|
+
console.log(`
|
|
567
|
+
dd-harness apagar ${endereco} --confirmar ${r.confirmacaoEsperada}`);
|
|
568
|
+
}
|
|
569
|
+
// Sem exit diferente de zero: recusar por falta de confirmacao nao e falha, e o
|
|
570
|
+
// caminho normal da primeira chamada.
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
console.log(`${endereco} apagada — ${r.detalhe}`);
|
|
574
|
+
}
|
|
480
575
|
async function comandoArquivar(argv) {
|
|
481
576
|
const endereco = argv[0];
|
|
482
577
|
const motivo = argumento(argv, "motivo");
|
|
@@ -952,6 +1047,12 @@ async function principal() {
|
|
|
952
1047
|
return comandoEditar(resto);
|
|
953
1048
|
case "arquivar":
|
|
954
1049
|
return comandoArquivar(resto);
|
|
1050
|
+
case "promover":
|
|
1051
|
+
return comandoPromover(resto, true);
|
|
1052
|
+
case "despromover":
|
|
1053
|
+
return comandoPromover(resto, false);
|
|
1054
|
+
case "apagar":
|
|
1055
|
+
return comandoApagar(resto);
|
|
955
1056
|
case "ler":
|
|
956
1057
|
return comandoLer(resto);
|
|
957
1058
|
case "buscar":
|
package/dist/cli/src/init.js
CHANGED
|
@@ -17,12 +17,19 @@ export const SUGESTAO_MCP = `{
|
|
|
17
17
|
}
|
|
18
18
|
}`;
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* Os dois hooks do dd-harness, pelas duas razoes que nenhuma instrucao em markdown
|
|
21
|
+
* resolve.
|
|
21
22
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* `CLAUDE.md`, porque instrucao o modelo
|
|
25
|
-
* silencio, e isso foi medido.
|
|
23
|
+
* `SessionStart` carrega a politica no inicio de cada sessao — a garantia de que nenhuma
|
|
24
|
+
* abre sem protocolo, papel que antes era do arquivo materializado mais a linha de
|
|
25
|
+
* import. Vive num hook, e nao numa instrucao no `CLAUDE.md`, porque instrucao o modelo
|
|
26
|
+
* pode pular: o import quebrado falhava em silencio, e isso foi medido.
|
|
27
|
+
*
|
|
28
|
+
* `PreToolUse` e o cinto: cruza o arquivo que esta sendo editado contra as ancoras e
|
|
29
|
+
* entrega a memoria ANTES da edicao. Existe porque o Brain era todo PULL, e PULL falha
|
|
30
|
+
* onde mais custa — o agente nao busca memoria quando *acha que sabe*. Os dois vao
|
|
31
|
+
* juntos aqui de proposito: quem monta o `settings.json` a mao pelo `init` sairia sem o
|
|
32
|
+
* cinto e sem nunca saber que ele existe.
|
|
26
33
|
*
|
|
27
34
|
* Sugerido e nao escrito, pelo mesmo motivo do `.mcp.json`.
|
|
28
35
|
*/
|
|
@@ -38,6 +45,17 @@ export const SUGESTAO_HOOK = `{
|
|
|
38
45
|
}
|
|
39
46
|
]
|
|
40
47
|
}
|
|
48
|
+
],
|
|
49
|
+
"PreToolUse": [
|
|
50
|
+
{
|
|
51
|
+
"matcher": "Edit|Write|MultiEdit",
|
|
52
|
+
"hooks": [
|
|
53
|
+
{
|
|
54
|
+
"type": "command",
|
|
55
|
+
"command": "dd-harness cinto"
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
41
59
|
]
|
|
42
60
|
}
|
|
43
61
|
}`;
|
package/dist/mcp/src/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import * as z from "zod/v4";
|
|
|
6
6
|
// que e gerado por build e nao vai no git — num checkout limpo (a Vercel) a resolucao
|
|
7
7
|
// falharia, e foi assim que o build de producao caiu uma vez. Aqui o compilador segue o
|
|
8
8
|
// fonte, e o `dist` deste pacote sai com o codigo do CLI embutido.
|
|
9
|
-
import { arquiva, edita, le } from "../../cli/src/curar.js";
|
|
9
|
+
import { apaga, arquiva, edita, le, promove } from "../../cli/src/curar.js";
|
|
10
10
|
import { busca } from "../../cli/src/buscar.js";
|
|
11
11
|
import { criaPasta } from "../../cli/src/pasta.js";
|
|
12
12
|
import { criaProjeto } from "../../cli/src/projeto.js";
|
|
@@ -77,12 +77,24 @@ Os primeiros achados são os que valem: o piso barra tema alheio, mas num Brain
|
|
|
77
77
|
// limite 10 devolvia dois tercos do Brain, e devolver quase tudo desfaz a
|
|
78
78
|
// recuperacao por relevancia. Quem chama a rota direto continua com 10.
|
|
79
79
|
const r = await busca(raiz, consulta, limite ?? 4);
|
|
80
|
+
// A busca responde igual com a base inteira indexada ou pela metade: memoria sem
|
|
81
|
+
// vetor some do resultado sem deixar rastro, e quem chamou conclui "nao existe".
|
|
82
|
+
// O CLI ja avisava; aqui ficava calado — e e no MCP que o silencio custa mais,
|
|
83
|
+
// porque e a porta que o agente usa sozinho, sem humano lendo a saida.
|
|
84
|
+
const fila = r.esperandoIndexacao > 0
|
|
85
|
+
? `\n\nATENÇÃO: ${r.esperandoIndexacao} memória(s) ainda sem vetor — pode ` +
|
|
86
|
+
"existir resposta melhor que não apareceu aqui. Rode o worker " +
|
|
87
|
+
"(`start-worker.bat` ou `pnpm worker --uma-vez`) e busque de novo antes de " +
|
|
88
|
+
"concluir que algo não está no Brain."
|
|
89
|
+
: "";
|
|
80
90
|
if (r.achados.length === 0) {
|
|
81
91
|
return texto(`Nada pertinente no Brain para "${consulta}".` +
|
|
82
|
-
(r.semantica ? "" : " (Busca lexical apenas: sem provedor de embedding configurado.)")
|
|
92
|
+
(r.semantica ? "" : " (Busca lexical apenas: sem provedor de embedding configurado.)") +
|
|
93
|
+
fila);
|
|
83
94
|
}
|
|
84
95
|
const linhas = r.achados.map((a) => `- ${a.endereco} — ${a.titulo}: ${a.resumo}`);
|
|
85
|
-
return texto(`${r.achados.length} achado(s)${r.semantica ? "" : " (busca lexical apenas)"}:\n${linhas.join("\n")}`
|
|
96
|
+
return texto(`${r.achados.length} achado(s)${r.semantica ? "" : " (busca lexical apenas)"}:\n${linhas.join("\n")}` +
|
|
97
|
+
fila);
|
|
86
98
|
}
|
|
87
99
|
catch (erro) {
|
|
88
100
|
return falha(erro);
|
|
@@ -218,6 +230,66 @@ Use \`substituida_por\` quando outra memória toma o lugar desta — a troca aco
|
|
|
218
230
|
return falha(erro);
|
|
219
231
|
}
|
|
220
232
|
});
|
|
233
|
+
server.registerTool("promover_memoria", {
|
|
234
|
+
description: `Torna uma memória GLOBAL: ela passa a valer para todo projeto deste espaço, inclusive os que ainda não existem.
|
|
235
|
+
|
|
236
|
+
PROPONHA ao humano e espere o OK. Promover não é editar — é decidir que uma lição vale para tudo que se faz aqui, e o alcance passa a incluir projetos que ninguém escreveu ainda.
|
|
237
|
+
|
|
238
|
+
Quando propor: a memória descreve algo que NÃO é deste projeto — um limite de fornecedor, uma regra da organização, uma armadilha da linguagem ou da ferramenta, uma decisão que vale para tudo que você constrói. O teste: se o próximo projeto repetir este erro, a memória teria evitado?
|
|
239
|
+
|
|
240
|
+
Quando NÃO propor: a memória fala de uma escolha deste projeto, de um arquivo deste repositório, de um acordo com um cliente específico. Isso é memória de projeto — e para alcançar alguns projetos nomeados existe \`projetos:\` no frontmatter, que é uma lista, não uma propriedade.
|
|
241
|
+
|
|
242
|
+
\`global: false\` traz de volta. É reversível, e a memória nunca sai da pasta onde nasceu: a origem é metade do porquê.`,
|
|
243
|
+
inputSchema: z.object({
|
|
244
|
+
endereco: z.string().min(1).describe("`<pasta>/<slug>` da memória."),
|
|
245
|
+
global: z
|
|
246
|
+
.boolean()
|
|
247
|
+
.default(true)
|
|
248
|
+
.describe("`true` promove a global; `false` traz de volta ao alcance dos vínculos."),
|
|
249
|
+
}),
|
|
250
|
+
}, async ({ endereco, global }) => {
|
|
251
|
+
try {
|
|
252
|
+
const r = await promove(raiz, endereco, global);
|
|
253
|
+
return texto(global
|
|
254
|
+
? `\`${endereco}\` agora é GLOBAL — vale para todo projeto de ${r.tenant}, ` +
|
|
255
|
+
"inclusive os que ainda não existem. Ela continua morando na pasta onde nasceu."
|
|
256
|
+
: `\`${endereco}\` voltou a valer só para os projetos a que está vinculada.`);
|
|
257
|
+
}
|
|
258
|
+
catch (erro) {
|
|
259
|
+
return falha(erro);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
server.registerTool("apagar_memoria", {
|
|
263
|
+
description: `Apaga uma memória DE VEZ, com as âncoras e toda a deriva medida delas. Não há desfazer.
|
|
264
|
+
|
|
265
|
+
Na maioria dos casos o certo é \`arquivar_memoria\`, não isto: arquivar tira do Brain ativo e guarda o conteúdo, porque o que a memória dizia pode voltar a importar. Apagar é para o que nunca deveria ter existido — a gravada por engano, a duplicata, o teste.
|
|
266
|
+
|
|
267
|
+
DUAS ETAPAS. Chame primeiro SEM \`confirmacao\`: a resposta diz o que a cascata levaria junto e qual palavra digitar. Leve isso ao humano e só chame de novo com a confirmação se ele disser para apagar. Nunca invente a confirmação para pular a pergunta — a etapa existe para que uma pessoa veja o custo antes.`,
|
|
268
|
+
inputSchema: z.object({
|
|
269
|
+
endereco: z.string().min(1).describe("`<pasta>/<slug>` da memória."),
|
|
270
|
+
confirmacao: z
|
|
271
|
+
.string()
|
|
272
|
+
.optional()
|
|
273
|
+
.describe("O nome do espaço, exatamente como a primeira chamada informou. Só mande depois de o humano confirmar."),
|
|
274
|
+
}),
|
|
275
|
+
}, async ({ endereco, confirmacao }) => {
|
|
276
|
+
try {
|
|
277
|
+
const r = await apaga(raiz, endereco, confirmacao);
|
|
278
|
+
if (!r.apagou) {
|
|
279
|
+
return texto(`${r.detalhe}` +
|
|
280
|
+
(r.confirmacaoEsperada
|
|
281
|
+
? `
|
|
282
|
+
|
|
283
|
+
Pergunte ao humano antes de seguir. Se ele confirmar, repita com ` +
|
|
284
|
+
`\`confirmacao: "${r.confirmacaoEsperada}"\`.`
|
|
285
|
+
: ""));
|
|
286
|
+
}
|
|
287
|
+
return texto(`\`${endereco}\` apagada — ${r.detalhe}`);
|
|
288
|
+
}
|
|
289
|
+
catch (erro) {
|
|
290
|
+
return falha(erro);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
221
293
|
server.registerTool("criar_projeto", {
|
|
222
294
|
description: `Cria um projeto no serviço — o espaço onde as pastas e memórias deste repositório vão morar.
|
|
223
295
|
|
package/package.json
CHANGED