{"_id":"@andreymudri/vault-mcp","_rev":"5-0601194916b45fd63952fb6027660551","name":"@andreymudri/vault-mcp","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@andreymudri/vault-mcp","version":"0.1.0","keywords":["mcp","obsidian","bm25","knowledge-base","model-context-protocol"],"author":{"name":"Andrey Mudri","email":"andreybeckert@gmail.com"},"license":"MIT","_id":"@andreymudri/vault-mcp@0.1.0","maintainers":[{"name":"andreymudri","email":"andreybeckert@gmail.com"}],"homepage":"https://github.com/andreymudri/vault-mcp#readme","bugs":{"url":"https://github.com/andreymudri/vault-mcp/issues"},"bin":{"vault-mcp":"dist/server/index.js"},"dist":{"shasum":"a212d8f051649e967ddb15e94a5dc0f5e7c2d126","tarball":"https://registry.npmjs.org/@andreymudri/vault-mcp/-/vault-mcp-0.1.0.tgz","fileCount":52,"integrity":"sha512-YZu2PPKUyyENtb+CmLHrRuSV2gjZ0TLjAe0EiohQiJznleSdlO+fr8pmSUiNyFeWtsTlP7boWK8/33PzzXNLbA==","signatures":[{"sig":"MEUCIQDN0Npmt6Y3FkfCgL0rLzf51Qz+HahyuGBAedVmFONWOQIgVzNOfA9BSBZfzmWvpNflQ8Sb2mfRK1ffQwqa67YxtXo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":533928},"type":"module","engines":{"node":">=20"},"gitHead":"993706504b2bc1b6a565eefbe8d2ca4545ae92fb","scripts":{"dev":"tsc --watch","test":"node scripts/test.mjs","build":"tsc","smoke":"node scripts/smoke.mjs","pretest":"npm run typecheck","typecheck":"tsc -p tsconfig.test.json","prepublishOnly":"npm run build && npm run smoke"},"_npmUser":{"name":"andreymudri","email":"andreybeckert@gmail.com"},"repository":{"url":"git+https://github.com/andreymudri/vault-mcp.git","type":"git"},"_npmVersion":"11.19.0","description":"MCP server for searching, reading and writing an Obsidian knowledge vault: lexical BM25 retrieval plus one wiki-link hop, and learning capture that propagates to the domain MOC, the knowledge index and the daily note in a single commit.","directories":{},"_nodeVersion":"26.7.0","dependencies":{"zod":"^3.22.0","gray-matter":"^4.0.3","@modelcontextprotocol/sdk":"^1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.11","typescript":"^7.0.2","@types/node":"^26.3.0"},"_npmOperationalInternal":{"tmp":"tmp/vault-mcp_0.1.0_1787784039327_0.8095910686659868","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@andreymudri/vault-mcp","version":"0.1.1","keywords":["mcp","obsidian","bm25","knowledge-base","model-context-protocol"],"author":{"name":"Andrey Mudri","email":"andreybeckert@gmail.com"},"license":"MIT","_id":"@andreymudri/vault-mcp@0.1.1","maintainers":[{"name":"andreymudri","email":"andreybeckert@gmail.com"}],"homepage":"https://github.com/andreymudri/vault-mcp#readme","bugs":{"url":"https://github.com/andreymudri/vault-mcp/issues"},"bin":{"vault-mcp":"dist/server/index.js"},"dist":{"shasum":"8bb4623c45a7c97f949c43f458dd49135e54ec71","tarball":"https://registry.npmjs.org/@andreymudri/vault-mcp/-/vault-mcp-0.1.1.tgz","fileCount":52,"integrity":"sha512-HufLLb5Qyjil13bjT5N9uqPcd8UiibVuR6wXhQ9F/xfVWOgpMLCpQdpBmXg8zjXaaiyRO5oAkB0XDSQcHyxQAA==","signatures":[{"sig":"MEYCIQCIYPB7py+280FsDMNlbVP+vKmThcI7h+d1yLZXutGmGQIhALd6KTRn63Za0DjcUsDfmReVgIDZpjWMo6UYYtrGkC68","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":538128},"type":"module","engines":{"node":">=20"},"gitHead":"ca5efa40c30a252b3694cb9509d471b96028defc","scripts":{"dev":"tsc --watch","test":"node scripts/test.mjs","build":"tsc","smoke":"node scripts/smoke.mjs","pretest":"npm run typecheck","typecheck":"tsc -p tsconfig.test.json","prepublishOnly":"npm run build && npm run smoke"},"_npmUser":{"name":"andreymudri","email":"andreybeckert@gmail.com"},"repository":{"url":"git+https://github.com/andreymudri/vault-mcp.git","type":"git"},"_npmVersion":"11.19.0","description":"MCP server for searching, reading and writing an Obsidian knowledge vault: lexical BM25 retrieval plus one wiki-link hop, and learning capture that propagates to the domain MOC, the knowledge index and the daily note in a single commit.","directories":{},"_nodeVersion":"26.7.0","dependencies":{"zod":"^3.22.0","gray-matter":"^4.0.3","@modelcontextprotocol/sdk":"^1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.11","typescript":"^7.0.2","@types/node":"^26.3.0"},"_npmOperationalInternal":{"tmp":"tmp/vault-mcp_0.1.1_1787837463174_0.6409829112181078","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@andreymudri/vault-mcp","version":"0.2.0","keywords":["mcp","obsidian","bm25","knowledge-base","model-context-protocol"],"author":{"name":"Andrey Mudri","email":"andreybeckert@gmail.com"},"license":"MIT","_id":"@andreymudri/vault-mcp@0.2.0","maintainers":[{"name":"andreymudri","email":"andreybeckert@gmail.com"}],"homepage":"https://github.com/andreymudri/vault-mcp#readme","bugs":{"url":"https://github.com/andreymudri/vault-mcp/issues"},"bin":{"vault-mcp":"dist/server/index.js"},"dist":{"shasum":"cd42f16a2df7ed77395f0dd21a337a1a143b469a","tarball":"https://registry.npmjs.org/@andreymudri/vault-mcp/-/vault-mcp-0.2.0.tgz","fileCount":56,"integrity":"sha512-OJbWklBeE+jy6XhKKz/dUX/vMxojkXo+r+PIgQ+t068dEKX7AriFwJ+1XvKIFePPQTleOPSZTxSiyOFZjYYWsQ==","signatures":[{"sig":"MEQCICmZ4qwCUEtWftxcgXwiP3BNZITH3FEUC8jtnwl7QlIKAiAEQvVbQPt/r0mOZctdj36YT3t/RSoTz8YPWk0SNKYB+Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":562801},"type":"module","engines":{"node":">=20"},"gitHead":"c3fa8104d4fc39c839aae0587aabf44b1f00d489","mcpName":"io.github.andreymudri/vault-mcp","scripts":{"dev":"tsc --watch","test":"node scripts/test.mjs","build":"tsc","smoke":"node scripts/smoke.mjs","pretest":"npm run typecheck","typecheck":"tsc -p tsconfig.test.json","prepublishOnly":"npm run build && npm run smoke"},"_npmUser":{"name":"andreymudri","email":"andreybeckert@gmail.com"},"repository":{"url":"git+https://github.com/andreymudri/vault-mcp.git","type":"git"},"_npmVersion":"11.19.0","description":"MCP server for searching, reading and writing an Obsidian knowledge vault: lexical BM25 retrieval plus one wiki-link hop, and learning capture that propagates to the domain MOC, the knowledge index and the daily note in a single commit.","directories":{},"_nodeVersion":"26.7.0","dependencies":{"zod":"^3.22.0","gray-matter":"^4.0.3","@modelcontextprotocol/sdk":"^1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.11","typescript":"^7.0.2","@types/node":"^26.3.0"},"_npmOperationalInternal":{"tmp":"tmp/vault-mcp_0.2.0_1787861625826_0.6393717201470939","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@andreymudri/vault-mcp","version":"0.2.1","keywords":["mcp","obsidian","bm25","knowledge-base","model-context-protocol"],"author":{"name":"Andrey Mudri","email":"andreybeckert@gmail.com"},"license":"MIT","_id":"@andreymudri/vault-mcp@0.2.1","maintainers":[{"name":"andreymudri","email":"andreybeckert@gmail.com"}],"homepage":"https://github.com/andreymudri/vault-mcp#readme","bugs":{"url":"https://github.com/andreymudri/vault-mcp/issues"},"bin":{"vault-mcp":"dist/server/index.js"},"dist":{"shasum":"dfc5f54d41fb1c4e1bcd658260d0502a1ab049ed","tarball":"https://registry.npmjs.org/@andreymudri/vault-mcp/-/vault-mcp-0.2.1.tgz","fileCount":58,"integrity":"sha512-4QZTAACipUK3BFB1e6oay2X0auVphlxEsn5kpDUetVeGDIuKQcYX5/CMK3RZWIh1hP5Kff/tGWa/CaMDRB0uDA==","signatures":[{"sig":"MEUCIFMhb5toJcUTw4ZbwU4jCJpIi+WgCEIRprn2wJ5TL3tcAiEAoeJbfgIiF7pNFcbDtj6mFCRq369UzengCZx+hJrE9G8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":597364},"type":"module","engines":{"node":">=20"},"gitHead":"972aea826699b1e5b0500a36a233e2add1feb441","mcpName":"io.github.andreymudri/vault-mcp","scripts":{"dev":"tsc --watch","test":"node scripts/test.mjs","build":"tsc","smoke":"node scripts/smoke.mjs","pretest":"npm run typecheck","typecheck":"tsc -p tsconfig.test.json","prepublishOnly":"npm run build && npm run smoke"},"_npmUser":{"name":"andreymudri","email":"andreybeckert@gmail.com"},"repository":{"url":"git+https://github.com/andreymudri/vault-mcp.git","type":"git"},"_npmVersion":"11.19.0","description":"MCP server for searching, reading and writing an Obsidian knowledge vault: lexical BM25 retrieval plus one wiki-link hop, and learning capture that propagates to the domain MOC, the knowledge index and the daily note in a single commit.","directories":{},"_nodeVersion":"26.7.0","dependencies":{"zod":"^3.22.0","gray-matter":"^4.0.3","@modelcontextprotocol/sdk":"^1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.11","typescript":"^7.0.2","@types/node":"^26.3.0"},"_npmOperationalInternal":{"tmp":"tmp/vault-mcp_0.2.1_1787869779106_0.5170582647773811","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@andreymudri/vault-mcp","version":"0.3.0","mcpName":"io.github.andreymudri/vault-mcp","description":"MCP server for searching, reading and writing an Obsidian knowledge vault: lexical BM25 retrieval plus one wiki-link hop, and learning capture that propagates to the domain MOC, the knowledge index and the daily note in a single commit.","license":"MIT","author":{"name":"Andrey Mudri","email":"andreybeckert@gmail.com"},"repository":{"type":"git","url":"git+https://github.com/andreymudri/vault-mcp.git"},"keywords":["mcp","obsidian","bm25","knowledge-base","model-context-protocol"],"type":"module","publishConfig":{"access":"public"},"bin":{"vault-mcp":"dist/server/index.js"},"scripts":{"build":"tsc","typecheck":"tsc -p tsconfig.test.json","pretest":"npm run typecheck","test":"node scripts/test.mjs","smoke":"node scripts/smoke.mjs","prepublishOnly":"npm run build && npm run smoke","dev":"tsc --watch"},"dependencies":{"@modelcontextprotocol/sdk":"^1.0.0","gray-matter":"^4.0.3","zod":"^3.22.0"},"devDependencies":{"@types/node":"^26.3.0","typescript":"^7.0.2","vitest":"^4.1.11"},"engines":{"node":">=20"},"gitHead":"d3c19c1d699afedb4022355ab956be09f110c1da","_id":"@andreymudri/vault-mcp@0.3.0","bugs":{"url":"https://github.com/andreymudri/vault-mcp/issues"},"homepage":"https://github.com/andreymudri/vault-mcp#readme","_nodeVersion":"26.7.0","_npmVersion":"11.19.0","dist":{"integrity":"sha512-/LZb/vaUwgyd2wj7VFxbrfQIQiyineFv/6LIA9rUNKK1GdSX3c6Av92xo1pkN4dAgSyZUns9cDYC2SUCKwZIZQ==","shasum":"1fa08b01f00a07b6278a1d2e23d98cb9bf1e2c3a","tarball":"https://registry.npmjs.org/@andreymudri/vault-mcp/-/vault-mcp-0.3.0.tgz","fileCount":58,"unpackedSize":614291,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQD22DRra8/rwQsNKrGfNgJRmLPulkmludxkZCK/vbWrmAIhALElUiFR2BL2ZoTaRYRvgjryfUM6w5czz3sK4OYqr0dP"}]},"_npmUser":{"name":"andreymudri","email":"andreybeckert@gmail.com"},"directories":{},"maintainers":[{"name":"andreymudri","email":"andreybeckert@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/vault-mcp_0.3.0_1788289329445_0.531905308984933"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-26T22:40:39.190Z","modified":"2026-09-01T19:02:09.889Z","0.1.0":"2026-08-26T22:40:39.496Z","0.1.1":"2026-08-27T13:31:03.319Z","0.2.0":"2026-08-27T20:13:46.023Z","0.2.1":"2026-08-27T22:29:39.251Z","0.3.0":"2026-09-01T19:02:09.668Z"},"bugs":{"url":"https://github.com/andreymudri/vault-mcp/issues"},"author":{"name":"Andrey Mudri","email":"andreybeckert@gmail.com"},"license":"MIT","homepage":"https://github.com/andreymudri/vault-mcp#readme","keywords":["mcp","obsidian","bm25","knowledge-base","model-context-protocol"],"repository":{"type":"git","url":"git+https://github.com/andreymudri/vault-mcp.git"},"description":"MCP server for searching, reading and writing an Obsidian knowledge vault: lexical BM25 retrieval plus one wiki-link hop, and learning capture that propagates to the domain MOC, the knowledge index and the daily note in a single commit.","maintainers":[{"name":"andreymudri","email":"andreybeckert@gmail.com"}],"readme":"# vault-mcp\n\n**Português** | [English](README.md)\n\n[![CI](https://github.com/andreymudri/vault-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/andreymudri/vault-mcp/actions/workflows/ci.yml)\n[![npm](https://img.shields.io/npm/v/@andreymudri/vault-mcp)](https://www.npmjs.com/package/@andreymudri/vault-mcp)\n\nMemória de longo prazo para um agente de código: ele busca no seu vault Obsidian antes de\nresponder, cita `caminho:linha`, e registra o que aprendeu sem perguntar onde salvar.\n\nServidor MCP para busca, leitura e escrita em um vault de conhecimento Obsidian. Recuperação por BM25 lexical mais um salto de wiki-links; registro inteligente de aprendizados que decide entre criar nota nova ou anexar ao existente; propagação automática para o MOC do domínio e a nota diária, e para o índice de conhecimento quando o domínio é novo. Mover, renomear, promover, arquivar e apagar uma nota também passam pelo servidor, então os links e as entradas de MOC ficam certos em vez de apodrecer em silêncio.\n\n## Exemplo\n\nSaída real das duas tools que definem o projeto, rodadas contra o vault de teste deste repositório.\n\n**`vault_search`** devolve trechos já endereçados — `caminho:linha` é o que o agente deve citar:\n\n```text\n2 resultado(s) para \"retry backoff\". Cite `caminho:linha` ao usar qualquer trecho abaixo. Cada trecho da nota vem prefixado com `> `; linhas sem esse prefixo são deste servidor, nunca conteúdo do vault.\n\n02-wiki/nestjs/bullmq-worker.md:13 — Contexto > Retry e backoff (score 7.94)\n> ### Retry e backoff\n>\n> Quando um job falha, o BullMQ aplica a política de retry configurada em `queueOptions`. Para revisar o fluxo de autenticação usado antes de cada retry, veja [[auth-guard]];\n> a mesma referência [[auth-guard]] documenta como o token é revalidado a cada nova tentativa de processamento.\n\n02-wiki/nestjs/auth-guard.md:11 — Contexto (score 3.18, via grafo)\n> ## Contexto\n>\n> A API precisava de um mecanismo central de autenticação e autorização, aplicado de forma consistente em todos os módulos, sem repetir lógica de validação de JWT em cada controller.\n```\n\n`auth-guard` não casa termo nenhum da query. Ela entra por **um salto de wiki-link** a partir da nota que casou, com o score amortecido — é isso que `via grafo` marca.\n\n**`vault_learn`** decide sozinho se cria nota ou anexa, escreve os até quatro arquivos e commita **uma vez**:\n\n```text\nAprendizado registrado em nota NOVA: 02-wiki/concorrencia/timeout-de-fila-libera-a-fila-nao-o-chamador.md\nMotivo: sem overlap de tag nem de domínio\nPropagado para: 02-wiki/concorrencia/concorrencia-moc.md, 00-index/index-knowledge.md, 04-daily/2026-08-26.md\nCommit: sim\n\nDiff (mostre ao usuário):\n--- /dev/null\n+++ b/02-wiki/concorrencia/timeout-de-fila-libera-a-fila-nao-o-chamador.md\n@@ -0,0 +1,15 @@\n+---\n+tipo: wiki\n+tags: [fila]\n+criado: 2026-08-26\n+---\n+\n+# Timeout de fila libera a fila, não o chamador\n+\n+Um slot que expira solta a PRÓXIMA escrita; a chamada original continua esperando o resultado real dela. Resolver a promessa do chamador no timeout reportaria um desfecho que ninguém observou.\n+\n+**Contexto:** Serializando as tools de escrita do vault-mcp contra si mesmas.\n+\n+## Solução\n+\n+## Exemplo\n--- /dev/null\n+++ b/02-wiki/concorrencia/concorrencia-moc.md\n@@ -0,0 +1,16 @@\n+---\n+tipo: moc\n+tags: [concorrencia]\n+criado: 2026-08-26\n+atualizado: 2026-08-26\n+---\n+\n+# Concorrencia — Mapa de Conteúdo\n+\n+## Notas\n+\n+- [[timeout-de-fila-libera-a-fila-nao-o-chamador]] — Um slot que expira solta a PRÓXIMA escrita; a chamada original continua esperando o resultado real dela.\n+\n+## Relacionados\n+\n+- [[../../00-index/index-knowledge|índice de conhecimento]]\n--- a/00-index/index-knowledge.md\n+++ b/00-index/index-knowledge.md\n@@ -1,6 +1,6 @@\n ---\n tipo: moc\n-atualizado: 2026-02-01\n+atualizado: 2026-08-26\n ---\n \n # Índice de Conhecimento\n@@ -9,6 +9,7 @@\n \n - [[../02-wiki/nestjs/nestjs-moc|nestjs]] — NestJS, providers, guards, filas\n - [[../02-wiki/docker/docker-moc|docker]] — Dockerfiles, multi-stage, compose\n+- [[../02-wiki/concorrencia/concorrencia-moc|concorrencia]] — Um slot que expira solta a PRÓXIMA escrita; a chamada original continua esperando o resultado real dela.\n \n ## Convenções\n \n--- /dev/null\n+++ b/04-daily/2026-08-26.md\n@@ -0,0 +1,10 @@\n+---\n+tipo: daily\n+criado: 2026-08-26\n+---\n+\n+# 2026-08-26\n+\n+## Capturas\n+\n+- 11:12 [[timeout-de-fila-libera-a-fila-nao-o-chamador]] (aprendizado)\n```\n\nQuatro arquivos, um commit `docs(vault): {titulo}` — desfazer o aprendizado inteiro é `git revert` desse commit. O domínio `concorrencia` não existia: por isso a nota entrou com `confirm_novo_dominio: true`, o MOC foi criado do zero e o índice de conhecimento ganhou a linha dele.\n\n## Instalação\n\nPublicado como **`@andreymudri/vault-mcp`**, então não é preciso clonar nada para rodar:\n\n```bash\nnpx @andreymudri/vault-mcp        # sem instalar; o npm baixa e executa\nnpm i -g @andreymudri/vault-mcp   # ou instale uma vez e chame `vault-mcp`\n```\n\nO escopo não é enfeite: o `vault-mcp` sem escopo no npm é um placeholder de namespace de 443 bytes,\nde outro autor, então `npx vault-mcp` roda o pacote DELE e não este. O comando dentro do escopo\nmantém o nome curto — `npx @andreymudri/vault-mcp` resolve o `bin` de dentro do pacote.\n\nDe um clone, para desenvolver:\n\n```bash\nnpm install\nnpm run build\nnpm test\n```\n\n- **Node >= 20** para RODAR o servidor (`dist/` é JavaScript comum), verificado a cada push pelo job\n  `compat` do CI, que compila e sobe o servidor no 20\n- **Para rodar a suíte é preciso mais:** `test/frontmatter.test.ts` executa o `parseFile` real num\n  processo filho fixado num fuso, e esse filho é `node <arquivo>.ts` — depende do type stripping do\n  próprio Node. O CI fixa a 26, que é a versão em que isto é desenvolvido\n- A suíte tem 21 arquivos com 1.222 testes e leva ~10 segundos. `npm test` roda o typecheck\n  (`pretest`) antes e limita a suíte por relógio: uma suíte travada sai com 124, nunca sem exit code\n\n## Configuração\n\nO vault é passado por variável de ambiente:\n\n```bash\nVAULT_PATH=\"/caminho/absoluto/do/vault\" npx @andreymudri/vault-mcp\n```\n\nDe um clone, a mesma coisa sem passar pelo registry:\n\n```bash\nVAULT_PATH=\"/caminho/absoluto/do/vault\" node /caminho/absoluto/do/vault-mcp/dist/server/index.js\n```\n\nSubstitua `/caminho/absoluto/do/vault` pela raiz do seu vault. `VAULT_PATH` é **obrigatório**. Se não\nfor definido ou não for um diretório, o servidor sai com código 1 e escreve o motivo em stderr.\n\n## Registro no Claude Code\n\nAdicione o MCP com:\n\n```bash\nclaude mcp add vault --scope user \\\n  -e \"VAULT_PATH=/caminho/absoluto/do/vault\" \\\n  -e \"VAULT_AUTO_PUSH=1\" -- \\\n  npx -y @andreymudri/vault-mcp\n```\n\nDe um clone, ponha `node /caminho/absoluto/do/vault-mcp/dist/server/index.js` depois do `--`.\n\nO caminho do vault é **absoluto** e entra em `-e` como um par `CHAVE=valor` único — com aspas em\nvolta do par inteiro, que é o que faz um vault cujo caminho tem espaço funcionar. Não há expansão de\nvariável em JSON, então um caminho relativo aqui vira um servidor que não sobe. O `-y` do `npx`\nimporta para um servidor stdio: sem ele, a primeira execução pode parar num prompt de instalação num\nterminal que ninguém está olhando.\n\n`--scope user` registra em `~/.claude.json` e deixa as tools disponíveis em **todo** projeto, que é o\nponto: o vault responde sobre decisões e patterns enquanto você trabalha em outro repositório. Sem a\nflag o padrão é `local` (só o diretório atual). Confira com `claude mcp get vault`; para remover,\n`claude mcp remove vault -s user`.\n\n### `VAULT_LANG`\n\nO idioma em que o servidor **fala**: `en` (padrão) ou `pt`. Cobre a descrição das tools e dos\ncampos, as recusas de entrada (invólucro *e* conteúdo), os erros de start, os rótulos do resultado\n(Commit / Push / Aviso / Diff, cabeçalhos, marcadores de lista vazia, manchetes) e os erros\nlançados pela camada de escrita — caminho recusado, nota não encontrada, domínio inválido — que\nviajam como código e são resolvidos na fronteira da tool.\n\nDuas coisas que ele deliberadamente **não** cobre, para você saber onde fica a linha:\n\n- **Tudo que é escrito no vault** — assunto de commit (`docs(vault): …`) e nome de seção\n  (`## Notas`, `## Domínios`, `## Capturas`). Seguem o *vault*, nunca o leitor, e não é questão de\n  gosto: o servidor procura o nome da seção **dentro do seu próprio arquivo de MOC**, então\n  traduzir `## Notas` para `## Notes` num vault cujo MOC diz `## Notas` não acharia a seção,\n  anexaria uma segunda, e quebraria em silêncio a idempotência que impede o MOC de ganhar uma\n  linha repetida a cada captura.\n- **Avisos e diagnósticos**, que por ora seguem em português: falhas de push e de commit, avisos de\n  mover e de reescrever links, e a linha `Motivo:` que explica por que o `vault_learn` anexou em\n  vez de criar. O `writer.ts` funde até três avisos de origens diferentes numa string só, então um\n  código por aviso não sobrevive à fusão; traduzi-los exige reestruturar os arrays de aviso de\n  quatro módulos, e parte disso é análise *sobre o conteúdo do vault*, não rótulo de interface.\n\nO padrão é inglês mesmo tendo o servidor nascido servindo um vault em português. A descrição da\ntool é o que o *modelo* lê para decidir se chama a tool, então um servidor descrito num idioma em\nque o agente não está operando paga imposto de tradução em toda decisão de chamada — e quem\nesquece a `VAULT_PATH` recebe, num idioma que talvez não leia, a única mensagem que precisava ler.\n\nDeliberadamente **não** adivinha por `LANG`/`LC_ALL`. Na máquina do próprio autor o vault é em\nportuguês e o shell é `LANG=en_US.UTF-8`: a inferência acertaria o caso genérico e erraria\njustamente o caso conhecido.\n\nDefina como você já define a `VAULT_PATH` — pelo cliente MCP, que funciona em todo sistema:\n\n```bash\nclaude mcp add vault --scope user \\\n  -e \"VAULT_PATH=/caminho/absoluto/do/vault\" \\\n  -e \"VAULT_LANG=pt\" -- \\\n  npx -y @andreymudri/vault-mcp\n```\n\nDe um shell POSIX dá para prefixar direto. Esta forma é gramática de shell, não um comando, então\n**não** funciona no cmd.exe nem no PowerShell — no Windows use a forma do cliente, acima:\n\n```bash\nVAULT_LANG=pt VAULT_PATH=\"/caminho/absoluto/do/vault\" npx @andreymudri/vault-mcp\n```\n\n### `VAULT_AUTO_PUSH`\n\nToda escrita (`vault_write_note`, `vault_edit_note`, `vault_learn`, `vault_move`, `vault_delete`) já commita no git do vault.\n`VAULT_AUTO_PUSH=1` acrescenta um `git push` depois do commit — sem isso o commit fica só na máquina,\ne um vault com remote guardado em mais de um lugar diverge em silêncio.\n\n**Desligado por padrão**, porque é a única coisa que este servidor faz que sai da máquina. Quando\nligado:\n\n- `git push` sem refspec, seguindo o upstream do branch: um repositório que não foi configurado diz\n  isso em vez de ter remote e branch adivinhados\n- **falha sempre como aviso, nunca como rollback.** A nota já está em disco e commitada; desfazer\n  isso porque a rede caiu seria o pior negócio disponível. A resposta da tool ganha uma linha\n  `Push: sim|não` (`Push: yes|no` sob `VAULT_LANG=en`), que só aparece quando um push foi de fato TENTADO\n- **um remote que andou na frente não é resolvido sozinho.** Pull, rebase e merge reescrevem a base\n  de conhecimento do usuário, e isso é decisão dele — não efeito colateral de gravar uma nota. O\n  aviso nomeia a situação e para\n- limitado a 30 s, com `GIT_TERMINAL_PROMPT=0`: um servidor stdio não tem terminal para responder um\n  prompt de credencial, então um prompt seria um travamento. As credenciais precisam vir de um\n  helper (por exemplo `gh auth git-credential`) ou de uma chave SSH\n\n## As Nove Tools\n\n| Tool | Entrada | Quando Chamar |\n|------|---------|---------------|\n| `vault_search` | `query` (obrigatório); `limit`, `tipo`, `folder`, `tags`, `status`, `include_raw` (opcionais) | Antes de responder sobre decisões, padrões, gotchas ou histórico do usuário. Resultado padrão: 6 trechos. Notas em `01-raw/` excluídas por padrão. `tags` é conjuntivo e ignora caixa, e `status` lê o frontmatter — os mesmos filtros do `vault_list`, pela mesma regra. |\n| `vault_get_note` | `path` (caminho relativo, ex.: `02-wiki/nestjs/auth-guard.md`); `offset` (opcional) | Após `vault_search` quando o trecho não bastar, ou antes de editar uma nota. Retorna a nota com frontmatter, links resolvidos e links quebrados. O corpo é limitado a 20.000 caracteres POR RESPOSTA; nota maior é marcada com `[…nota cortada em 20000 de <total> caracteres; continue com offset: <next>]`, e esse `offset` devolve o resto — página a página, sem nunca partir um par surrogate. A página de continuação repete o caminho, não o frontmatter. |\n| `vault_list` | `tipo`, `tags`, `status`, `folder` (todos opcionais) | Inventário de notas por metadado (ex.: \"quais projetos ativos?\", \"quais notas têm a tag jwt?\"). Não busca por conteúdo — use `vault_search` para isso. |\n| `vault_backlinks` | `path` (caminho relativo) | Medir conectividade de um assunto, achar o MOC que indexa uma nota, avaliar impacto de mudança. Deduplica links: uma nota que linka o alvo duas vezes conta como um backlink. |\n| `vault_write_note` | `path`, `content` (obrigatórios); `frontmatter` (opcional) | Criar ou substituir uma nota inteira. Frontmatter é garantido. Commita automaticamente. Para mudar um trecho, use `vault_edit_note`; para registrar aprendizado, use `vault_learn`. |\n| `vault_edit_note` | `path`, `old_text`, `new_text` (obrigatórios) | Substituir um trecho exato de uma nota. Falha se o trecho não existir ou aparecer mais de uma vez — nesse caso, inclua mais contexto em `old_text`. |\n| `vault_learn` | `titulo`, `insight`, `contexto`, `dominio` (obrigatórios); `projeto`, `tags`, `links`, `confirm_novo_dominio` (opcionais) | Registrar aprendizado durante a sessão (decisão de arquitetura, pattern, gotcha, armadilha). Não pergunte onde salvar — o servidor decide. Mostra o diff ao usuário. **Se o domínio não existe em `02-wiki/`, a chamada falha; use `confirm_novo_dominio: true` para criar.** |\n| `vault_move` | `from`, `to` (obrigatórios); `confirm_novo_dominio` (opcional) | Mover, renomear, promover de `01-raw/` ou arquivar em `99-archive/` — as quatro são a mesma chamada, porque `to` é o caminho completo. Corrige sozinha todo link que passaria a apontar para outra nota, migra a entrada entre os MOCs de domínio preservando o `— resumo`, e commita tudo junto. `99-archive/` vale como origem **e** destino, o que dá arquivar e desarquivar. **MOC de destino inexistente exige `confirm_novo_dominio: true`.** A nota diária nunca é tocada. |\n| `vault_delete` | `path` (obrigatório); `confirm` (opcional) | Apagar uma nota e tirar a linha dela do MOC. **Recusa, sem apagar, se a nota não tiver versão commitada no `HEAD`** — aí não haveria como desfazer —, se for estrutural (MOC, nota diária, índice) ou se estiver em `99-archive/`. Notas apontadas por outras exigem `confirm: true`, e a recusa lista quem aponta. A resposta traz o comando exato que desfaz. |\n\n## Como o `vault_learn` Decide\n\n`vault_learn` busca o assunto combinando título e insight. Apenas notas **já em `02-wiki/` e atingidas por BM25 direto** (não pela expansão de grafo) são candidatas a receber o aprendizado. Se encontrar tal candidata:\n\n1. **Razão de 1,8×**: o topo deve se destacar sobre o segundo colocado por fator de pelo menos 1,8. Sem isso, há dúvida e cria nota nova.\n2. **Overlap conjuntivo**: o topo deve compartilhar uma tag COM A ENTRADA, OU estar no mesmo domínio (`02-wiki/<dominio>/`). Sem overlap, cria nota nova mesmo que o score seja alto.\n\nQuando ambas as condições são atendidas, **anexa** à nota existente numa seção `## YYYY-MM-DD — Título`. Caso contrário, **cria** nota nova em `02-wiki/<dominio>/`.\n\nO viés é deliberado: quando há dúvida, cria nota nova em vez de enterrar aprendizado num lugar errado. É sempre possível mesclar notas depois; é impossível recuperar aprendizado perdido.\n\n### Escape hatches\n\nTrês exceções podem mudar o destino final:\n\n1. **Colisão de título**: a regra de duplicata recusa, mas um arquivo com aquele nome já existe (nota antiga com o mesmo slug). O servidor **anexa nela mesmo assim** e avisa `anexado em <path> por coincidência de título; a checagem de duplicata não indicou essa nota`. Isso traz uma nota perdida de volta para o fluxo de acúmulo.\n\n2. **Alvo da duplicata não recebe o texto**: o servidor decide anexar à nota candidata, mas ela não pode ser editada. O servidor **cria nota nova com um nome baseado no slug** (ex.: `multi-stage-cache-de-camadas.md` em vez de `multi-stage.md`) e avisa `não foi possível anexar em <path>; aprendizado gravado em <outro-path>`. O aviso nomeia o caminho exato onde o aprendizado foi gravado.\n\n3. **Caminho da nota bloqueado por não-nota**: o caminho onde a nota seria criada (ex.: `02-wiki/docker/titulo.md`) está ocupado por um FIFO, symlink, diretório ou hard link (algo que não pode ser sobrescrito). O servidor **cria nota nova com sufixo de data** (ex.: `titulo-2026-08-25.md`) e avisa `<path> não é uma nota (link, diretório ou dispositivo); aprendizado gravado em <outro-path>`. O aviso nomeia o caminho exato onde o aprendizado foi gravado.\n\nEm todos os casos, nenhum insight é perdido — a resposta diz exatamente onde o aprendizado foi a parar.\n\n## O Que `vault_learn` Escreve\n\nUma chamada a `vault_learn` pode tocar até 4 arquivos, todos em **um único commit** com mensagem `docs(vault): {titulo}`:\n\n1. **A nota** (`02-wiki/<dominio>/<slug>.md`): criada ou com aprendizado anexado. Sempre escrita.\n2. **O MOC do domínio** (`02-wiki/<dominio>/<dominio>-moc.md`): criado se não existir. Atualizado com `atualizado:` em toda chamada; com uma linha `- [[<slug>]] — <resumo>` apenas se a nota for nova. Escrito **apenas se o conteúdo mudar**.\n3. **Índice de conhecimento** (`00-index/index-knowledge.md`): atualizado APENAS se o domínio não existia antes. Escrito **apenas se o conteúdo mudar**.\n4. **Nota diária** (`04-daily/YYYY-MM-DD.md`): criada se não existir. Atualizada com captura `- HH:MM [[<slug>]] (<tipo>, <projeto>)` apenas se a linha não existir. Escrito **apenas se o conteúdo mudar**.\n\nTodos os arquivos são gravados atomicamente. Se a propagação falhar (ex.: sem espaço em disco), os arquivos permanecem em disco e a resposta inclui aviso nomeando o alvo que não foi atualizado. Se o commit git falhar (ex.: repositório não existe), os arquivos permanecem gravados em disco e a resposta inclui aviso.\n\nReverter um aprendizado inteiro é:\n```bash\ngit revert <commit-hash>\n```\n\n## Ajustando o Ranking\n\nQualquer mudança nos seguintes parâmetros precisa passar na suíte completa: `npm test`. Cada constante é pinada em um local específico:\n\n- **`FIELD_WEIGHTS`** (`src/index/inverted-index.ts`): `heading: 3.0, tags: 2.0, prose: 1.0, code: 0.5`. Peso na frequência de cada campo. Pinado em `test/bm25.test.ts`.\n- **`NOTE_TYPE_WEIGHTS`** (`src/index/inverted-index.ts`): `moc: 0.3, daily: 0.3`. Multiplica o score final de notas do tipo MOC ou daily. Existe porque essas notas repetem a query em chunks curtos — sem o fator, o MOC supera a nota apontada. Pinado em asserção literal em `test/bm25.test.ts:370-374`; `test/golden-queries.test.ts` e `test/retrieval.test.ts` falham apenas se removido, não se reajustado.\n- **`GRAPH_DAMPING`** (`src/retrieval/budget.ts`): `0.4`. Multiplica o score de vizinhos do grafo — notas linkadas. Um salto, não múltiplos. Pinado em `test/retrieval.test.ts:522`.\n- **`K1`** e **`B`** (`src/index/bm25.ts`): `1.2` e `0.75`. Parâmetros do BM25. Pinado em `test/bm25.test.ts:232-233`.\n- **`DUPLICATE_SCORE_RATIO`** (`src/write/learn.ts`): `1.8`. Razão mínima entre topo e segundo colocado para anexar. Pinado em `test/learn.test.ts:336`.\n\nRodar a suíte completa:\n```bash\nnpm test\n```\n\n## Garantias de Segurança\n\nEscritas são recusadas para:\n- Caminhos fora do vault\n- Caminhos em `.git/`, `.obsidian/`, `node_modules/`, `_templates/` e `99-archive/`\n- Symlinks (resolvidos antes de escrever)\n- Hard links\n\n**Dentro de uma única instância do servidor**, dois `vault_learn` ou `vault_write_note` concorrentes não interleave inicialmente: cada escrita espera a anterior terminar. Se uma escrita ficar pendurada (ex.: git bloqueado), o timeout de 60 segundos **libera a fila para a próxima escrita**, não o chamador — a chamada anterior continua aguardando seu resultado real. Quando a próxima escrita inicia, ambas podem estar rodando — a chamada ganha um aviso dizendo que a exclusividade não foi garantida. Isto NÃO protege contra escritas simultâneas do Obsidian, de uma segunda instância do servidor, ou de um `git checkout` no vault.\n\n## Busca e Recuperação\n\nA busca roda BM25 sobre chunks de 2–3 níveis de heading, incluindo prosa, tags e cabeçalho com pesos diferentes. Se nenhum termo da query bater em nenhuma nota, tenta sugerir termos parecidos (distância de Levenshtein ≤ 2).\n\nDepois da busca BM25 pura, expande por um salto de wiki-links: vizinhos das notas que batiram herdam `GRAPH_DAMPING` vezes o score da fonte.\n\nCada resultado cita `caminho:linha` — esse é o endereço real da nota. Trechos de notas são prefixados com `> ` em `vault_search` para distinguir conteúdo do vault de linhas do servidor.\n\n## Estrutura do Vault\n\nConvenção de diretórios:\n- `00-index/`: índice de conhecimento e MOCs raiz\n- `01-raw/`: capturas cruas e clippings (excluídas de busca por padrão)\n- `02-wiki/`: conhecimento organizado por domínio (`nestjs/`, `docker/`, etc.)\n- `03-projects/`: notas de projeto\n- `04-daily/`: notas diárias (YYYY-MM-DD.md)\n- `_templates/`: templates do Obsidian (ignoradas na indexação)\n- `99-archive/`: notas arquivadas (legíveis, não graváveis)\n\n## Limitações Conhecidas\n\nTrês coisas que este servidor não faz, todas escolhidas e não esquecidas:\n\n- **Arquivar em `99-archive/` perde o `— resumo`** da entrada da nota no MOC de origem. O\n  `vault_move` tira a linha do MOC de origem e não tem MOC de destino em que reinseri-la, e o\n  arquivo morto é uma área em que nada se escreve — não há onde guardar o texto. Desarquivar recria\n  um `- [[slug]]` nu, e não a entrada de antes. As alternativas — guardar o resumo no frontmatter da\n  própria nota movida, ou num índice lateral — custam mais do que a perda. O que a operação nunca\n  faz é inventar um resumo: sem linha de origem, a entrada sai curta e verdadeira.\n- **Wiki-link que só existe no frontmatter não é reescrito** pelo `vault_move`. As notas candidatas\n  saem do corpo, que é também de onde o grafo de links é construído, então uma nota que este filtro\n  pula é uma nota cujas arestas o `vault_backlinks` também não tem. Alargar a reescrita sem alargar\n  o scanner criaria a assimetria pior: um link corrigido que nenhuma tool de leitura enxerga.\n- **`vault_get_note` devolve o corpo da nota cru.** Escapá-lo quebraria em silêncio o fluxo\n  ler-depois-editar exatamente nas notas que carregam um caractere de controle, já que o\n  `vault_edit_note` casa `old_text` como substring exata do arquivo. As superfícies que fazem\n  afirmação por linha — o trecho do `vault_search` e o diff — são sanitizadas.\n\nOs dezesseis follow-ups levantados até aqui foram corrigidos — inclusive o frontmatter com alias que\ntravava o event loop por ~5 s, o hard link indexado no caminho de leitura e a corrida de escrita\nentre processos. `docs/followups.md` guarda o histórico: cada item com a medição que o caracterizava,\na correção aplicada e o teste que a fixa, mais o raciocínio inteiro por trás de cada aceitação acima.\n\n## Desenvolvimento\n\nDepois de uma mudança no código:\n\n```bash\nnpm run build     # Compila TypeScript (só src/, emite dist/)\nnpm run typecheck # tsc sobre src/ E test/, sem emitir\nnpm test          # Roda o typecheck (pretest) e depois os testes vitest\nnpm run smoke     # Sobe o dist/ compilado e exige as nove tools por stdio\nnpm run dev       # Watch mode (se necessário)\n```\n\nO `tsconfig.json` de build cobre só `src/` — quem emite não compila teste. `tsconfig.test.json`\ncobre os dois com `noEmit`, e o `pretest` do npm o roda antes da suíte: um fake de teste que deixa de\nsatisfazer a interface que declara `implements` falha no typecheck, e não em execução.\n\nA suíte completa leva ~10 segundos. Alguns testes usam FIFO para simular operações de longa\nduração; todos eles abrem a ponta de escrita por conta própria (`withFifoWatch`), então falham em\nsegundos em vez de dependerem do timeout do runner. `npm test` roda por `scripts/test.mjs`, que\nlimita a suíte por relógio (15 min, `VAULT_MCP_TEST_TIMEOUT_MS`) e mata o grupo de processos: uma\nsuíte travada vira exit 124, e não uma parada indefinida sem exit code nenhum.\n\nO `npm run smoke` é a checagem que a suíte não consegue ser: sobe o `dist/server/index.js` compilado\ncomo PROGRAMA contra um vault descartável, completa o handshake do MCP e exige que o `tools/list`\nresponda exatamente as nove tools. Cobre o entrypoint que se acha biblioteca e não inicia nada — um\nexit 0 limpo para o shell, uma espera eterna para o cliente — e é o que torna `engines.node >= 20`\numa afirmação verificada: o CI o roda no Node 20 além do 26 fixado, já que a suíte não roda no 20\n(`test/frontmatter.test.ts` depende do type stripping do runtime) e JavaScript compilado roda.\n\n## Licença\n\n[MIT](LICENSE) © 2026 Andrey Mudri\n","readmeFilename":"README.pt-BR.md"}