dd-harness-mcp 0.14.0 → 0.16.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 +10 -4
- package/dist/cli/src/curar.js +50 -0
- package/dist/cli/src/index.js +136 -6
- package/dist/cli/src/init.js +23 -5
- package/dist/cli/src/skill.js +143 -0
- package/dist/cli/src/skills-iniciais.js +72 -0
- package/dist/mcp/src/index.js +109 -1
- package/package.json +1 -1
package/dist/cli/src/cinto.js
CHANGED
|
@@ -66,13 +66,14 @@ export async function guardaCache(raiz, brain) {
|
|
|
66
66
|
gravado_em: new Date().toISOString(),
|
|
67
67
|
memorias: brain.memorias
|
|
68
68
|
.filter((m) => m.status === "ativa" && m.ancoras.length > 0)
|
|
69
|
-
.map(({ pasta, slug, titulo, resumo, status, ancoras }) => ({
|
|
69
|
+
.map(({ pasta, slug, titulo, resumo, status, ancoras, escopo }) => ({
|
|
70
70
|
pasta,
|
|
71
71
|
slug,
|
|
72
72
|
titulo,
|
|
73
73
|
resumo,
|
|
74
74
|
status,
|
|
75
75
|
ancoras,
|
|
76
|
+
escopo,
|
|
76
77
|
})),
|
|
77
78
|
};
|
|
78
79
|
try {
|
|
@@ -143,7 +144,7 @@ export function caminhoRelativo(raiz, arquivo) {
|
|
|
143
144
|
* Vazio e o caso comum e tem que sair barato: a maioria das edicoes nao toca ancora
|
|
144
145
|
* alguma, e o silencio ai nao e falta de aviso, e a ausencia de motivo para avisar.
|
|
145
146
|
*/
|
|
146
|
-
export function alerta(tocadas, resumos) {
|
|
147
|
+
export function alerta(tocadas, resumos, globais = new Set()) {
|
|
147
148
|
if (tocadas.length === 0)
|
|
148
149
|
return "";
|
|
149
150
|
const linhas = [
|
|
@@ -152,7 +153,11 @@ export function alerta(tocadas, resumos) {
|
|
|
152
153
|
];
|
|
153
154
|
for (const t of tocadas) {
|
|
154
155
|
const endereco = `${t.pasta}/${t.memoria}`;
|
|
155
|
-
|
|
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}\``, "");
|
|
156
161
|
const resumo = resumos.get(endereco);
|
|
157
162
|
if (resumo)
|
|
158
163
|
linhas.push(resumo, "");
|
|
@@ -203,11 +208,12 @@ export async function decideDoHook(entrada) {
|
|
|
203
208
|
if (novas.length === 0)
|
|
204
209
|
return permitir;
|
|
205
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}`));
|
|
206
212
|
return JSON.stringify({
|
|
207
213
|
hookSpecificOutput: {
|
|
208
214
|
hookEventName: "PreToolUse",
|
|
209
215
|
permissionDecision: "allow",
|
|
210
|
-
additionalContext: alerta(novas, resumos),
|
|
216
|
+
additionalContext: alerta(novas, resumos, globais),
|
|
211
217
|
},
|
|
212
218
|
});
|
|
213
219
|
}
|
package/dist/cli/src/curar.js
CHANGED
|
@@ -92,3 +92,53 @@ 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
|
+
projeto: config.projeto,
|
|
131
|
+
...(confirmacao ? { confirmacao } : {}),
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
// 409 aqui nao e falha: e "ainda nao" — falta confirmar, ou ha uma arquivada apontando
|
|
135
|
+
// para esta. O texto do servidor e a resposta, entao ele passa adiante em vez de virar
|
|
136
|
+
// excecao com mensagem generica.
|
|
137
|
+
if (resposta.status === 409) {
|
|
138
|
+
const lido = (await resposta.json());
|
|
139
|
+
return { apagou: false, detalhe: lido.erro, confirmacaoEsperada: lido.confirmacao_esperada };
|
|
140
|
+
}
|
|
141
|
+
if (!resposta.ok)
|
|
142
|
+
await recusa(resposta);
|
|
143
|
+
return (await resposta.json());
|
|
144
|
+
}
|
package/dist/cli/src/index.js
CHANGED
|
@@ -21,8 +21,10 @@ import { pergunta, escolha, fechaPerguntas } from "./pergunta.js";
|
|
|
21
21
|
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
|
+
import { escrevePonteirosDeSkills, leSkills, semeiaSkills } from "./skill.js";
|
|
25
|
+
import { SKILLS_INICIAIS } from "./skills-iniciais.js";
|
|
24
26
|
import { busca } from "./buscar.js";
|
|
25
|
-
import { arquiva, edita, le } from "./curar.js";
|
|
27
|
+
import { apaga, arquiva, edita, le, promove } from "./curar.js";
|
|
26
28
|
import { criaPasta } from "./pasta.js";
|
|
27
29
|
import { criaProjeto, listaTenants } from "./projeto.js";
|
|
28
30
|
import { reancora } from "./reancorar.js";
|
|
@@ -52,6 +54,15 @@ const AJUDA = `dd-harness — a política e o Brain do projeto, no serviço
|
|
|
52
54
|
dd-harness arquivar <pasta>/<slug> --motivo <obsoleta|incorreta|fora_dos_filtros>
|
|
53
55
|
[--substituida-por <pasta>/<slug>]
|
|
54
56
|
tira de circulação sem apagar
|
|
57
|
+
dd-harness promover <pasta>/<slug>
|
|
58
|
+
torna a memória global: vale para TODO
|
|
59
|
+
projeto do espaço, inclusive os futuros
|
|
60
|
+
dd-harness despromover <pasta>/<slug>
|
|
61
|
+
traz de volta ao alcance dos vínculos
|
|
62
|
+
dd-harness apagar <pasta>/<slug> [--confirmar <espaço>]
|
|
63
|
+
apaga de vez, em cascata — sem desfazer.
|
|
64
|
+
Para tirar de circulação guardando o
|
|
65
|
+
conteúdo, use "arquivar"
|
|
55
66
|
dd-harness buscar "<pergunta>" acha memória por relevância, não por arquivo
|
|
56
67
|
dd-harness ler <pasta>/<slug> imprime a memória inteira, no formato de gravar
|
|
57
68
|
dd-harness reancorar <pasta>/<slug> --de "<alvo>" --para "<alvo>"
|
|
@@ -180,6 +191,10 @@ async function comandoFase(argv) {
|
|
|
180
191
|
* primeira conexao do Claude Code. Sem isto, a primeira instalacao acontecia so quando
|
|
181
192
|
* o host tentava conectar ao servidor MCP, e o handshake tem timeout curto demais para
|
|
182
193
|
* esperar o download: a sessao real via `CONNECTION_CLOSED` em vez de "instalando".
|
|
194
|
+
*
|
|
195
|
+
* Roda em TODO `start`, nao so no primeiro: e tambem o ponto em que o cache do npx e
|
|
196
|
+
* renovado para a versao mais recente. Um projeto ja configurado que roda `start` de novo
|
|
197
|
+
* sai dali com CLI e MCP frescos, sem ninguem precisar lembrar de atualizar nada.
|
|
183
198
|
*/
|
|
184
199
|
function instalaMcp() {
|
|
185
200
|
return new Promise((resolve) => {
|
|
@@ -187,7 +202,14 @@ function instalaMcp() {
|
|
|
187
202
|
// segundos) e, se falhar, ve POR QUE — em vez do "nao consegui" mudo que nao dava
|
|
188
203
|
// pista nenhuma para diagnosticar. O DEP0190 que isto dispara e suprimido no topo
|
|
189
204
|
// do arquivo (`process.removeAllListeners("warning")`).
|
|
190
|
-
|
|
205
|
+
//
|
|
206
|
+
// `@latest` EXPLICITO, e nao `dd-harness-mcp` pelado: sem a tag, o npx serve a copia
|
|
207
|
+
// que ja estiver no cache dele e nem consulta o registro. Medido na rodada 006 — a
|
|
208
|
+
// cobaia rodou uma sessao inteira contra um MCP defasado sem nada denunciar. O
|
|
209
|
+
// `.mcp.json` continua declarando o pacote SEM versao, de proposito: o que precisa
|
|
210
|
+
// estar fresco e o cache, e fixar versao no arquivo obrigaria a reescrever config em
|
|
211
|
+
// todo repositorio a cada publicacao.
|
|
212
|
+
const p = spawn("npx", ["-y", "dd-harness-mcp@latest", "--help"], {
|
|
191
213
|
shell: process.platform === "win32",
|
|
192
214
|
stdio: "inherit",
|
|
193
215
|
});
|
|
@@ -221,6 +243,32 @@ async function versaoDesatualizada() {
|
|
|
221
243
|
return null;
|
|
222
244
|
}
|
|
223
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Instala a versao mais nova do CLI globalmente, de dentro do proprio wizard.
|
|
248
|
+
*
|
|
249
|
+
* Ate aqui o `start` so AVISAVA e mandava o usuario rodar `npm i -g` e recomecar — e
|
|
250
|
+
* "comece de novo" no meio de um wizard e o tipo de instrucao que ninguem segue. A
|
|
251
|
+
* rodada 006 mostrou o custo: a sessao inteira rodou contra um CLI defasado, com o
|
|
252
|
+
* relatorio registrando "ja instalado, nao atualizado" como se fosse normal.
|
|
253
|
+
*
|
|
254
|
+
* O binario EM EXECUCAO continua sendo o antigo ate este processo terminar — nao ha como
|
|
255
|
+
* trocar codigo de baixo de si mesmo. Isso e seguro aqui porque o que resta do wizard so
|
|
256
|
+
* escreve configuracao (JSON e markdown), nada que dependa de correcao nova; e do
|
|
257
|
+
* proximo comando em diante tudo ja e a versao nova.
|
|
258
|
+
*
|
|
259
|
+
* Nunca lanca: falhar em atualizar nao pode travar o `start`. Sem permissao para escrever
|
|
260
|
+
* no diretorio global (o caso mais comum), o wizard segue e o aviso continua valendo.
|
|
261
|
+
*/
|
|
262
|
+
function atualizaCli(ultima) {
|
|
263
|
+
return new Promise((resolve) => {
|
|
264
|
+
const p = spawn("npm", ["install", "-g", `dd-harness@${ultima}`], {
|
|
265
|
+
shell: process.platform === "win32",
|
|
266
|
+
stdio: "inherit",
|
|
267
|
+
});
|
|
268
|
+
p.on("exit", (code) => resolve(code === 0));
|
|
269
|
+
p.on("error", () => resolve(false));
|
|
270
|
+
});
|
|
271
|
+
}
|
|
224
272
|
/**
|
|
225
273
|
* Tenta abrir o navegador padrao. Nunca lanca — falhar em abrir nao pode travar o
|
|
226
274
|
* wizard, so degrada para "aqui esta a URL, abra voce mesmo".
|
|
@@ -271,9 +319,14 @@ async function comandoStart() {
|
|
|
271
319
|
// pre-instalacao do MCP, passo 5.5 abaixo, numa versao anterior a ela existir).
|
|
272
320
|
const desatualizado = await versaoDesatualizada();
|
|
273
321
|
if (desatualizado) {
|
|
274
|
-
console.log(`
|
|
275
|
-
|
|
276
|
-
|
|
322
|
+
console.log(`Atualizando o dd-harness: ${desatualizado.atual} → ${desatualizado.ultima}...`);
|
|
323
|
+
const atualizou = await atualizaCli(desatualizado.ultima);
|
|
324
|
+
console.log(atualizou
|
|
325
|
+
? `Pronto — ${desatualizado.ultima} instalada. O wizard segue na ${desatualizado.atual} ` +
|
|
326
|
+
"(o binário em execução não troca sozinho); do próximo comando em diante, a nova.\n"
|
|
327
|
+
: `Não consegui atualizar — o npm explicou o motivo acima. Você segue na ` +
|
|
328
|
+
`${desatualizado.atual}, e a mais recente é ${desatualizado.ultima}: rode ` +
|
|
329
|
+
"`npm install -g dd-harness@latest` quando puder. O start continua assim mesmo.\n");
|
|
277
330
|
}
|
|
278
331
|
// 1. Credencial. Sem ela nada do resto e possivel — nem listar tenant.
|
|
279
332
|
// O estado aparece SEMPRE, mesmo quando ja existe credencial: antes o passo era
|
|
@@ -366,7 +419,8 @@ async function comandoStart() {
|
|
|
366
419
|
}[mcp.estado]);
|
|
367
420
|
}
|
|
368
421
|
if (mcp.ok) {
|
|
369
|
-
console.log("\nInstalando
|
|
422
|
+
console.log("\nInstalando a versão mais recente do servidor dd-harness-mcp " +
|
|
423
|
+
"(a primeira vez pode demorar um pouco)...");
|
|
370
424
|
const { ok: instalou, erro: erroInstalacao } = await instalaMcp();
|
|
371
425
|
console.log(instalou
|
|
372
426
|
? "dd-harness-mcp instalado — a próxima sessão do Claude Code conecta na hora."
|
|
@@ -387,6 +441,33 @@ async function comandoStart() {
|
|
|
387
441
|
acrescentado: `atualizado ${arquivo} (apontamento acrescentado ao que já existia)`,
|
|
388
442
|
}[r.estado]);
|
|
389
443
|
}
|
|
444
|
+
// 6.5. Os ponteiros das skills do projeto.
|
|
445
|
+
//
|
|
446
|
+
// O Claude Code descobre skill lendo `.claude/skills/*/SKILL.md` na ABERTURA da sessao,
|
|
447
|
+
// e servidor MCP nao fornece skill — entao o arquivo em disco e obrigatorio. O que ele
|
|
448
|
+
// carrega e so o frontmatter mais a chamada a `ler_skill`: o procedimento fica no
|
|
449
|
+
// servico, e corrigi-lo corrige em todos os repositorios de uma vez.
|
|
450
|
+
//
|
|
451
|
+
// Nunca falha o `start`: projeto sem skill e o caso comum, e nao ter skill nao impede
|
|
452
|
+
// nada do resto.
|
|
453
|
+
try {
|
|
454
|
+
// Projeto novo nasce com as skills iniciais, como nasce com politica e briefing. Num
|
|
455
|
+
// projeto que ja tem as suas, isto nao faz nada.
|
|
456
|
+
const { criadas } = await semeiaSkills(process.cwd(), SKILLS_INICIAIS);
|
|
457
|
+
if (criadas.length > 0) {
|
|
458
|
+
console.log(`criada(s) ${criadas.length} skill(s) inicial(is): ${criadas.join(", ")}`);
|
|
459
|
+
}
|
|
460
|
+
const skills = await leSkills(process.cwd());
|
|
461
|
+
if (skills.length > 0) {
|
|
462
|
+
const { escritos } = await escrevePonteirosDeSkills(process.cwd(), skills);
|
|
463
|
+
console.log(escritos > 0
|
|
464
|
+
? `escritos ${escritos} ponteiro(s) de skill em .claude/skills/`
|
|
465
|
+
: `${skills.length} skill(s) — ponteiros já estavam em dia`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
// Serviço fora do ar ou projeto recém-criado: o `start` segue.
|
|
470
|
+
}
|
|
390
471
|
// 7. Onde esta o monorepo do worker nesta maquina? So pergunta uma vez, e so importa
|
|
391
472
|
// se houver memoria na fila agora — pular aqui nao trava nada, so avisa mais vezes.
|
|
392
473
|
const semWorkerConfigurado = !(await temWorkerConfigurado());
|
|
@@ -477,6 +558,49 @@ async function comandoEditar(argv) {
|
|
|
477
558
|
console.log(` ${r.ancoras} âncora(s).`);
|
|
478
559
|
}
|
|
479
560
|
const MOTIVOS = ["obsoleta", "incorreta", "fora_dos_filtros"];
|
|
561
|
+
/**
|
|
562
|
+
* `promover` / `despromover` — o alcance da memoria.
|
|
563
|
+
*
|
|
564
|
+
* Global vale para TODO projeto do espaco, inclusive os que ainda nao existem. Nao e o
|
|
565
|
+
* mesmo que `projetos:` no frontmatter, que lista projetos nomeados: la o alcance e uma
|
|
566
|
+
* lista, aqui e uma propriedade.
|
|
567
|
+
*/
|
|
568
|
+
async function comandoPromover(argv, global) {
|
|
569
|
+
const endereco = argv[0];
|
|
570
|
+
const verbo = global ? "promover" : "despromover";
|
|
571
|
+
if (!endereco || endereco.startsWith("-") || !endereco.includes("/")) {
|
|
572
|
+
throw new Error(`uso: dd-harness ${verbo} <pasta>/<slug>`);
|
|
573
|
+
}
|
|
574
|
+
const r = await promove(process.cwd(), endereco, global);
|
|
575
|
+
console.log(global
|
|
576
|
+
? `${endereco} agora é GLOBAL — vale para todo projeto de ${r.tenant}, inclusive os que ainda não existem.`
|
|
577
|
+
: `${endereco} voltou a valer só para os projetos a que está vinculada.`);
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* `apagar` — o unico caminho que destroi.
|
|
581
|
+
*
|
|
582
|
+
* Duas etapas, e a segunda pede o nome do espaco digitado. Nao e cerimonia: a cascata leva
|
|
583
|
+
* ancoras e toda a deriva medida delas, e nao ha desfazer. Arquivar continua sendo o
|
|
584
|
+
* caminho normal — isto e para o que nunca deveria ter existido.
|
|
585
|
+
*/
|
|
586
|
+
async function comandoApagar(argv) {
|
|
587
|
+
const endereco = argv[0];
|
|
588
|
+
if (!endereco || endereco.startsWith("-") || !endereco.includes("/")) {
|
|
589
|
+
throw new Error("uso: dd-harness apagar <pasta>/<slug> [--confirmar <espaço>]");
|
|
590
|
+
}
|
|
591
|
+
const r = await apaga(process.cwd(), endereco, argumento(argv, "confirmar"));
|
|
592
|
+
if (!r.apagou) {
|
|
593
|
+
console.log(r.detalhe);
|
|
594
|
+
if (r.confirmacaoEsperada) {
|
|
595
|
+
console.log(`
|
|
596
|
+
dd-harness apagar ${endereco} --confirmar ${r.confirmacaoEsperada}`);
|
|
597
|
+
}
|
|
598
|
+
// Sem exit diferente de zero: recusar por falta de confirmacao nao e falha, e o
|
|
599
|
+
// caminho normal da primeira chamada.
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
console.log(`${endereco} apagada — ${r.detalhe}`);
|
|
603
|
+
}
|
|
480
604
|
async function comandoArquivar(argv) {
|
|
481
605
|
const endereco = argv[0];
|
|
482
606
|
const motivo = argumento(argv, "motivo");
|
|
@@ -952,6 +1076,12 @@ async function principal() {
|
|
|
952
1076
|
return comandoEditar(resto);
|
|
953
1077
|
case "arquivar":
|
|
954
1078
|
return comandoArquivar(resto);
|
|
1079
|
+
case "promover":
|
|
1080
|
+
return comandoPromover(resto, true);
|
|
1081
|
+
case "despromover":
|
|
1082
|
+
return comandoPromover(resto, false);
|
|
1083
|
+
case "apagar":
|
|
1084
|
+
return comandoApagar(resto);
|
|
955
1085
|
case "ler":
|
|
956
1086
|
return comandoLer(resto);
|
|
957
1087
|
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
|
}`;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { cabecalhos, credencial, pede, recusa } from "./api.js";
|
|
4
|
+
// --- rede ---
|
|
5
|
+
export async function leSkills(raiz) {
|
|
6
|
+
const { config, token } = await credencial(raiz);
|
|
7
|
+
const url = new URL(`${config.api}/api/v1/skills`);
|
|
8
|
+
url.searchParams.set("tenant", config.tenant);
|
|
9
|
+
url.searchParams.set("projeto", config.projeto);
|
|
10
|
+
const resposta = await pede(url, { headers: cabecalhos(token) });
|
|
11
|
+
if (!resposta.ok)
|
|
12
|
+
await recusa(resposta);
|
|
13
|
+
return (await resposta.json()).skills;
|
|
14
|
+
}
|
|
15
|
+
export async function leSkill(raiz, slug) {
|
|
16
|
+
const { config, token } = await credencial(raiz);
|
|
17
|
+
const url = new URL(`${config.api}/api/v1/skills/${encodeURIComponent(slug)}`);
|
|
18
|
+
url.searchParams.set("tenant", config.tenant);
|
|
19
|
+
url.searchParams.set("projeto", config.projeto);
|
|
20
|
+
const resposta = await pede(url, { headers: cabecalhos(token) });
|
|
21
|
+
if (!resposta.ok)
|
|
22
|
+
await recusa(resposta);
|
|
23
|
+
return (await resposta.json()).skill;
|
|
24
|
+
}
|
|
25
|
+
export async function criaSkill(raiz,
|
|
26
|
+
// Só o que a API aceita na criação: `atualizada_em` é do servidor, e aceitá-la no tipo
|
|
27
|
+
// prometeria um campo que o Zod descarta em silêncio.
|
|
28
|
+
dados) {
|
|
29
|
+
const { config, token } = await credencial(raiz);
|
|
30
|
+
const resposta = await pede(`${config.api}/api/v1/skills`, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: cabecalhos(token, true),
|
|
33
|
+
body: JSON.stringify({ tenant: config.tenant, projeto: config.projeto, ...dados }),
|
|
34
|
+
});
|
|
35
|
+
if (!resposta.ok)
|
|
36
|
+
await recusa(resposta);
|
|
37
|
+
return (await resposta.json());
|
|
38
|
+
}
|
|
39
|
+
// --- o ponteiro em disco (puro, para ser testado sem rede) ---
|
|
40
|
+
/**
|
|
41
|
+
* Escapa o que quebraria o YAML do frontmatter.
|
|
42
|
+
*
|
|
43
|
+
* A descricao vem de texto livre editado na interface: `: ` no meio dela faz o parser ler
|
|
44
|
+
* uma chave nova, e uma quebra de linha encerra o valor. Aspas duplas com escape resolvem
|
|
45
|
+
* os dois — e um frontmatter quebrado nao da erro visivel, a skill simplesmente some da
|
|
46
|
+
* lista.
|
|
47
|
+
*/
|
|
48
|
+
function comoEscalarYaml(texto) {
|
|
49
|
+
return `"${texto.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r?\n/g, " ")}"`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* O SKILL.md que vai para o disco: frontmatter de verdade, corpo que aponta para o MCP.
|
|
53
|
+
*
|
|
54
|
+
* `allowed-tools` sempre inclui a ferramenta que busca o procedimento — sem ela o ponteiro
|
|
55
|
+
* pediria permissao para ler a propria skill, e um "permitir?" antes de cada invocacao
|
|
56
|
+
* ensina a desligar o mecanismo.
|
|
57
|
+
*/
|
|
58
|
+
export function ponteiroDaSkill(skill) {
|
|
59
|
+
const frontmatter = [
|
|
60
|
+
"---",
|
|
61
|
+
`name: ${skill.slug}`,
|
|
62
|
+
`description: ${comoEscalarYaml(skill.descricao)}`,
|
|
63
|
+
];
|
|
64
|
+
if (skill.so_por_comando)
|
|
65
|
+
frontmatter.push("disable-model-invocation: true");
|
|
66
|
+
if (skill.dica_de_argumento) {
|
|
67
|
+
frontmatter.push(`argument-hint: ${comoEscalarYaml(skill.dica_de_argumento)}`);
|
|
68
|
+
}
|
|
69
|
+
const ferramentas = ["mcp__dd-harness__ler_skill", ...skill.ferramentas];
|
|
70
|
+
frontmatter.push(`allowed-tools: ${ferramentas.join(", ")}`);
|
|
71
|
+
if (skill.caminhos.length > 0) {
|
|
72
|
+
frontmatter.push(`paths: ${skill.caminhos.join(", ")}`);
|
|
73
|
+
}
|
|
74
|
+
frontmatter.push("---");
|
|
75
|
+
return `${frontmatter.join("\n")}
|
|
76
|
+
|
|
77
|
+
# ${skill.slug}
|
|
78
|
+
|
|
79
|
+
O procedimento desta skill vive no dd-harness, não neste arquivo.
|
|
80
|
+
|
|
81
|
+
**Chame a ferramenta MCP \`ler_skill\` com \`slug: "${skill.slug}"\` e siga o que ela
|
|
82
|
+
devolver.** É a primeira ação — não tente executar a skill a partir deste arquivo, que é
|
|
83
|
+
só o ponteiro.
|
|
84
|
+
|
|
85
|
+
Se a ferramenta não existir, o servidor MCP do dd-harness não está declarado: avise o
|
|
86
|
+
usuário em vez de improvisar um procedimento.
|
|
87
|
+
`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Escreve os ponteiros de todas as skills do projeto.
|
|
91
|
+
*
|
|
92
|
+
* NAO apaga diretorio que sobrou: uma skill apagada no servico deixa o ponteiro orfao, e
|
|
93
|
+
* varrer `.claude/skills/` inteiro levaria junto as skills que a pessoa escreveu a mao —
|
|
94
|
+
* que sao legitimas e nao passam por aqui. O DELETE da API avisa o que apagar; a decisao
|
|
95
|
+
* de apagar arquivo alheio nao e nossa.
|
|
96
|
+
*/
|
|
97
|
+
export async function escrevePonteirosDeSkills(raiz, skills) {
|
|
98
|
+
let escritos = 0;
|
|
99
|
+
for (const skill of skills) {
|
|
100
|
+
const dir = join(raiz, ".claude", "skills", skill.slug);
|
|
101
|
+
const caminho = join(dir, "SKILL.md");
|
|
102
|
+
const novo = ponteiroDaSkill(skill);
|
|
103
|
+
// Só escreve quando muda: reescrever igual sujaria o `git status` a cada `start`.
|
|
104
|
+
const atual = await readFile(caminho, "utf8").catch(() => null);
|
|
105
|
+
if (atual === novo)
|
|
106
|
+
continue;
|
|
107
|
+
await mkdir(dir, { recursive: true });
|
|
108
|
+
await writeFile(caminho, novo, "utf8");
|
|
109
|
+
escritos++;
|
|
110
|
+
}
|
|
111
|
+
return { escritos };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Semeia as skills iniciais — so num projeto que ainda nao tem nenhuma.
|
|
115
|
+
*
|
|
116
|
+
* A checagem e "nenhuma skill", e nao "esta skill especifica": quem apagou
|
|
117
|
+
* `como-desenvolver` tomou uma decisao, e recria-la a cada `start` a desfaria em silencio.
|
|
118
|
+
* Projeto com qualquer skill propria ja passou do ponto de ser semeado.
|
|
119
|
+
*
|
|
120
|
+
* Devolve o que criou. Nunca lanca por falha de rede: semear e conveniencia de projeto
|
|
121
|
+
* novo, e um `start` nao pode falhar porque o mimo nao pode ser entregue.
|
|
122
|
+
*/
|
|
123
|
+
export async function semeiaSkills(raiz, iniciais) {
|
|
124
|
+
try {
|
|
125
|
+
const existentes = await leSkills(raiz);
|
|
126
|
+
if (existentes.length > 0)
|
|
127
|
+
return { criadas: [] };
|
|
128
|
+
const criadas = [];
|
|
129
|
+
for (const nova of iniciais) {
|
|
130
|
+
try {
|
|
131
|
+
await criaSkill(raiz, nova);
|
|
132
|
+
criadas.push(nova.slug);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Uma skill que nao entrou nao impede as outras — nem o `start`.
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { criadas };
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return { criadas: [] };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export const SKILLS_INICIAIS = [
|
|
2
|
+
{
|
|
3
|
+
slug: "como-desenvolver",
|
|
4
|
+
descricao: `Como desenvolver neste projeto — simplicidade, modularização, mudanças cirúrgicas e execução orientada a objetivos. Invoque ao escrever ou editar código para garantir qualidade. Não cobre segurança nem o protocolo de briefing (isso é sempre obrigatório, vive no CLAUDE.md).`,
|
|
5
|
+
conteudo: `# Como Desenvolver
|
|
6
|
+
|
|
7
|
+
Aplique ao escrever ou editar código.
|
|
8
|
+
|
|
9
|
+
## 1. Simplicidade Primeiro
|
|
10
|
+
|
|
11
|
+
**Mínimo de código que resolve o problema. Nada especulativo.**
|
|
12
|
+
|
|
13
|
+
- Sem funcionalidades além do que foi pedido.
|
|
14
|
+
- Sem abstrações para código de uso único.
|
|
15
|
+
- Sem "flexibilidade" ou "configurabilidade" que ninguém pediu.
|
|
16
|
+
- Sem tratamento de erro para cenários impossíveis.
|
|
17
|
+
- Se escreveu 200 linhas e dava em 50, reescreva.
|
|
18
|
+
|
|
19
|
+
O teste: *"Um engenheiro sênior diria que isso está complicado demais?"* Se sim, simplifique.
|
|
20
|
+
|
|
21
|
+
> Simplicidade e modularização (Seção 2) não brigam. Você **divide o que já existe e cresce** — não cria camadas para um futuro hipotético. Extrair um módulo de algo que repete é simplificar; criar um módulo "por via das dúvidas" é a complexidade especulativa que esta seção proíbe.
|
|
22
|
+
|
|
23
|
+
## 2. Modularização e Componentização
|
|
24
|
+
|
|
25
|
+
**Prefira peças pequenas e com responsabilidade única a um monólito grande.** Vale para qualquer código — front-end, back-end, scripts.
|
|
26
|
+
|
|
27
|
+
Um arquivo ou função que faz coisa demais é difícil de ler, testar, reusar e mudar sem quebrar o resto. Quando algo cresce, divida:
|
|
28
|
+
|
|
29
|
+
- **Uma responsabilidade por unidade.** Um arquivo, uma função, um componente deve ter um motivo só para mudar. Se você descreve o que ele faz usando "e" várias vezes, ele faz coisa demais.
|
|
30
|
+
- **Separe as camadas.** Não misture lógica de negócio, acesso a dados e apresentação no mesmo lugar. Cada uma muda por razões diferentes.
|
|
31
|
+
- **Extraia o que repete ou o que cresce** — só depois que existe de fato (ver Seção 1). A regra prática: na **segunda** vez que o mesmo trecho aparece, considere extrair; na terceira, extraia.
|
|
32
|
+
- **No front-end:** quebre telas/páginas grandes em componentes menores e nomeados. Um componente que rola por centenas de linhas quase sempre é vários componentes disfarçados.
|
|
33
|
+
|
|
34
|
+
O teste: *"Eu preciso rolar muito para entender esta unidade, ou guardar várias coisas na cabeça ao mesmo tempo?"* Se sim, divida.
|
|
35
|
+
|
|
36
|
+
**Mas não fragmente à toa.** Dividir em peças minúsculas demais cria o problema oposto — saltar entre dez arquivos para seguir uma linha de raciocínio. Divida quando a unidade carrega mais de uma responsabilidade, não para perseguir uma contagem de linhas.
|
|
37
|
+
|
|
38
|
+
## 3. Mudanças Cirúrgicas
|
|
39
|
+
|
|
40
|
+
**Toque apenas no necessário. Limpe apenas sua própria bagunça.**
|
|
41
|
+
|
|
42
|
+
Ao editar código existente:
|
|
43
|
+
- Não "melhore" código adjacente, comentários ou formatação que não fazem parte da tarefa.
|
|
44
|
+
- Não refatore o que não está quebrado.
|
|
45
|
+
- Mantenha o estilo existente, mesmo que você faria diferente.
|
|
46
|
+
- Viu código morto não relacionado? **Mencione — não delete.**
|
|
47
|
+
|
|
48
|
+
Quando suas mudanças criam órfãos:
|
|
49
|
+
- Remova imports/variáveis/funções que **suas** mudanças tornaram inúteis.
|
|
50
|
+
- Não remova código morto pré-existente sem ser solicitado.
|
|
51
|
+
|
|
52
|
+
O teste: cada linha alterada deve rastrear diretamente à solicitação do usuário.
|
|
53
|
+
|
|
54
|
+
## 4. Execução Orientada a Objetivos
|
|
55
|
+
|
|
56
|
+
**Defina critérios de sucesso. Itere até verificar.**
|
|
57
|
+
|
|
58
|
+
Transforme tarefas vagas em objetivos verificáveis:
|
|
59
|
+
- "Adicionar validação" → "Escreva testes para entradas inválidas, depois faça-os passar."
|
|
60
|
+
- "Corrigir o bug" → "Escreva um teste que o reproduza, depois faça-o passar."
|
|
61
|
+
- "Refatorar X" → "Garanta que os testes passem antes e depois."
|
|
62
|
+
|
|
63
|
+
Para tarefas com múltiplos passos, declare um plano breve:
|
|
64
|
+
\`\`\`
|
|
65
|
+
1. [Passo] → verificar: [checagem]
|
|
66
|
+
2. [Passo] → verificar: [checagem]
|
|
67
|
+
3. [Passo] → verificar: [checagem]
|
|
68
|
+
\`\`\`
|
|
69
|
+
|
|
70
|
+
Critérios fortes deixam você iterar sozinho. Critérios fracos ("faça funcionar") forçam esclarecimentos constantes.`,
|
|
71
|
+
},
|
|
72
|
+
];
|
package/dist/mcp/src/index.js
CHANGED
|
@@ -6,13 +6,14 @@ 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";
|
|
13
13
|
import { escreveArtefato, leArtefato } from "../../cli/src/artefato.js";
|
|
14
14
|
import { grava } from "../../cli/src/gravar.js";
|
|
15
15
|
import { caminhosDaArvore } from "../../cli/src/diff.js";
|
|
16
|
+
import { leSkill, leSkills } from "../../cli/src/skill.js";
|
|
16
17
|
import { avisoDeOrdem, criaFase, editaFase, formataChangelog, formataRoadmap, leFases, } from "../../cli/src/roadmap.js";
|
|
17
18
|
/**
|
|
18
19
|
* O mesmo servico, outra porta.
|
|
@@ -100,6 +101,53 @@ Os primeiros achados são os que valem: o piso barra tema alheio, mas num Brain
|
|
|
100
101
|
return falha(erro);
|
|
101
102
|
}
|
|
102
103
|
});
|
|
104
|
+
server.registerTool("ler_skill", {
|
|
105
|
+
description: `O procedimento de uma skill deste projeto. É o que o arquivo \`.claude/skills/<slug>/SKILL.md\` manda chamar — ele é só o ponteiro, o conteúdo vive aqui.
|
|
106
|
+
|
|
107
|
+
Chame com o \`slug\` que o ponteiro informou e SIGA o que vier, como se estivesse escrito no próprio arquivo. Não improvise o procedimento a partir do nome da skill.
|
|
108
|
+
|
|
109
|
+
Também serve para consultar uma skill sem invocá-la ("como é mesmo o passo a passo de X?"). \`listar_skills\` mostra o que existe.`,
|
|
110
|
+
inputSchema: z.object({
|
|
111
|
+
slug: z
|
|
112
|
+
.string()
|
|
113
|
+
.min(1)
|
|
114
|
+
.describe("O nome da skill, como aparece no `name` do ponteiro e no comando `/`."),
|
|
115
|
+
}),
|
|
116
|
+
}, async ({ slug }) => {
|
|
117
|
+
try {
|
|
118
|
+
const s = await leSkill(raiz, slug);
|
|
119
|
+
if (!s.conteudo.trim()) {
|
|
120
|
+
return texto(`A skill \`${slug}\` existe mas está sem procedimento escrito. Avise o usuário ` +
|
|
121
|
+
"em vez de improvisar: uma skill vazia é um passo que alguém pretendia definir.");
|
|
122
|
+
}
|
|
123
|
+
return texto(`# ${s.slug}
|
|
124
|
+
|
|
125
|
+
${s.conteudo.trim()}`);
|
|
126
|
+
}
|
|
127
|
+
catch (erro) {
|
|
128
|
+
return falha(erro);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
server.registerTool("listar_skills", {
|
|
132
|
+
description: `As skills deste projeto: nome, quando cada uma serve, e se é invocável só por comando.
|
|
133
|
+
|
|
134
|
+
Devolve a descrição de cada uma, não o procedimento — para o procedimento, \`ler_skill\`. Use quando o usuário perguntar o que existe, ou quando precisar saber se já há skill para um trabalho antes de fazê-lo à mão.`,
|
|
135
|
+
inputSchema: z.object({}),
|
|
136
|
+
}, async () => {
|
|
137
|
+
try {
|
|
138
|
+
const skills = await leSkills(raiz);
|
|
139
|
+
if (skills.length === 0) {
|
|
140
|
+
return texto("Este projeto não tem skill alguma — e isso é válido. Skill é procedimento " +
|
|
141
|
+
"repetido; um que aconteceu uma vez é só uma tarefa.");
|
|
142
|
+
}
|
|
143
|
+
const linhas = skills.map((s) => `- \`${s.slug}\`${s.so_por_comando ? " (só por comando)" : ""}: ${s.descricao}`);
|
|
144
|
+
return texto(`${skills.length} skill(s) neste projeto:\n${linhas.join("\n")}\n\n` +
|
|
145
|
+
"`ler_skill` traz o procedimento de uma delas.");
|
|
146
|
+
}
|
|
147
|
+
catch (erro) {
|
|
148
|
+
return falha(erro);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
103
151
|
server.registerTool("sugerir_ancoras", {
|
|
104
152
|
description: `Os arquivos que esta sessão modificou, para escolher a âncora de uma memória que vai ser gravada.
|
|
105
153
|
|
|
@@ -230,6 +278,66 @@ Use \`substituida_por\` quando outra memória toma o lugar desta — a troca aco
|
|
|
230
278
|
return falha(erro);
|
|
231
279
|
}
|
|
232
280
|
});
|
|
281
|
+
server.registerTool("promover_memoria", {
|
|
282
|
+
description: `Torna uma memória GLOBAL: ela passa a valer para todo projeto deste espaço, inclusive os que ainda não existem.
|
|
283
|
+
|
|
284
|
+
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.
|
|
285
|
+
|
|
286
|
+
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?
|
|
287
|
+
|
|
288
|
+
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.
|
|
289
|
+
|
|
290
|
+
\`global: false\` traz de volta. É reversível, e a memória nunca sai da pasta onde nasceu: a origem é metade do porquê.`,
|
|
291
|
+
inputSchema: z.object({
|
|
292
|
+
endereco: z.string().min(1).describe("`<pasta>/<slug>` da memória."),
|
|
293
|
+
global: z
|
|
294
|
+
.boolean()
|
|
295
|
+
.default(true)
|
|
296
|
+
.describe("`true` promove a global; `false` traz de volta ao alcance dos vínculos."),
|
|
297
|
+
}),
|
|
298
|
+
}, async ({ endereco, global }) => {
|
|
299
|
+
try {
|
|
300
|
+
const r = await promove(raiz, endereco, global);
|
|
301
|
+
return texto(global
|
|
302
|
+
? `\`${endereco}\` agora é GLOBAL — vale para todo projeto de ${r.tenant}, ` +
|
|
303
|
+
"inclusive os que ainda não existem. Ela continua morando na pasta onde nasceu."
|
|
304
|
+
: `\`${endereco}\` voltou a valer só para os projetos a que está vinculada.`);
|
|
305
|
+
}
|
|
306
|
+
catch (erro) {
|
|
307
|
+
return falha(erro);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
server.registerTool("apagar_memoria", {
|
|
311
|
+
description: `Apaga uma memória DE VEZ, com as âncoras e toda a deriva medida delas. Não há desfazer.
|
|
312
|
+
|
|
313
|
+
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.
|
|
314
|
+
|
|
315
|
+
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.`,
|
|
316
|
+
inputSchema: z.object({
|
|
317
|
+
endereco: z.string().min(1).describe("`<pasta>/<slug>` da memória."),
|
|
318
|
+
confirmacao: z
|
|
319
|
+
.string()
|
|
320
|
+
.optional()
|
|
321
|
+
.describe("O nome do espaço, exatamente como a primeira chamada informou. Só mande depois de o humano confirmar."),
|
|
322
|
+
}),
|
|
323
|
+
}, async ({ endereco, confirmacao }) => {
|
|
324
|
+
try {
|
|
325
|
+
const r = await apaga(raiz, endereco, confirmacao);
|
|
326
|
+
if (!r.apagou) {
|
|
327
|
+
return texto(`${r.detalhe}` +
|
|
328
|
+
(r.confirmacaoEsperada
|
|
329
|
+
? `
|
|
330
|
+
|
|
331
|
+
Pergunte ao humano antes de seguir. Se ele confirmar, repita com ` +
|
|
332
|
+
`\`confirmacao: "${r.confirmacaoEsperada}"\`.`
|
|
333
|
+
: ""));
|
|
334
|
+
}
|
|
335
|
+
return texto(`\`${endereco}\` apagada — ${r.detalhe}`);
|
|
336
|
+
}
|
|
337
|
+
catch (erro) {
|
|
338
|
+
return falha(erro);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
233
341
|
server.registerTool("criar_projeto", {
|
|
234
342
|
description: `Cria um projeto no serviço — o espaço onde as pastas e memórias deste repositório vão morar.
|
|
235
343
|
|
package/package.json
CHANGED