dd-harness-mcp 0.13.0 → 0.14.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.
@@ -25,6 +25,41 @@ 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 = {
@@ -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,
@@ -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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-harness-mcp",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "description": "Servidor MCP do dd-harness: o agente consulta e grava memoria como ferramenta, sem passar por arquivo.",
6
6
  "license": "UNLICENSED",