dd-harness-mcp 0.1.0 → 0.1.2
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/argv.js +27 -0
- package/dist/cli/src/check.js +1 -0
- package/dist/cli/src/diff.js +7 -1
- package/dist/cli/src/gravar.js +11 -2
- package/dist/cli/src/index.js +37 -1
- package/dist/cli/src/politica.js +35 -0
- package/package.json +40 -41
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leitura de argumentos da linha de comando.
|
|
3
|
+
*
|
|
4
|
+
* Vive fora do `index.ts` porque aquele modulo executa o CLI ao ser importado
|
|
5
|
+
* (`principal()` no topo) — teste que o importasse rodaria o programa inteiro.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Os termos livres de uma consulta, sem os pares `--flag valor`.
|
|
9
|
+
*
|
|
10
|
+
* Filtrar so o que comeca com `--` deixava o VALOR da flag para tras, e ele entrava na
|
|
11
|
+
* consulta: `buscar "efeito" --limite 2` virava a pergunta `"efeito 2"`. A busca roda, a
|
|
12
|
+
* resposta parece valida e responde a outra coisa — quem chamou nao tem como notar.
|
|
13
|
+
*/
|
|
14
|
+
export function termosDaConsulta(argv) {
|
|
15
|
+
const termos = [];
|
|
16
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
17
|
+
const atual = argv[i];
|
|
18
|
+
if (atual.startsWith("--")) {
|
|
19
|
+
// `--flag=valor` carrega o valor no proprio token; `--flag valor` consome o proximo.
|
|
20
|
+
if (!atual.includes("="))
|
|
21
|
+
i += 1;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
termos.push(atual);
|
|
25
|
+
}
|
|
26
|
+
return termos.join(" ").trim();
|
|
27
|
+
}
|
package/dist/cli/src/check.js
CHANGED
|
@@ -72,6 +72,7 @@ export async function status(raiz) {
|
|
|
72
72
|
vencidas: ativas
|
|
73
73
|
.filter((m) => m.revisar_ate && new Date(m.revisar_ate).getTime() < agora)
|
|
74
74
|
.map((m) => ({ pasta: m.pasta, memoria: m.slug, titulo: m.titulo })),
|
|
75
|
+
travadasNaFila: brain.travadas_na_fila ?? 0,
|
|
75
76
|
};
|
|
76
77
|
}
|
|
77
78
|
export async function check(raiz, commit) {
|
package/dist/cli/src/diff.js
CHANGED
|
@@ -30,6 +30,11 @@ export async function caminhosDoCommit(raiz, commit) {
|
|
|
30
30
|
/**
|
|
31
31
|
* Cruza caminhos com ancoras. A ancora casa quando e o proprio caminho ou quando e um
|
|
32
32
|
* diretorio que o contem — `supabase/migrations` tem que casar com a migration nova.
|
|
33
|
+
*
|
|
34
|
+
* Ancora de trecho (`arquivo.js#alvo`) casa pelo ARQUIVO: o `git diff-tree` devolve
|
|
35
|
+
* caminho puro, nunca com `#`, entao comparar o valor inteiro nao casava nunca — e em
|
|
36
|
+
* silencio, porque nao casar e resultado valido. O alvo continua no `Tocada.ancora`, que
|
|
37
|
+
* e o que a mensagem mostra: quem le precisa saber qual trecho a memoria guarda.
|
|
33
38
|
*/
|
|
34
39
|
export function memoriasTocadas(brain, caminhos) {
|
|
35
40
|
const tocadas = [];
|
|
@@ -37,7 +42,8 @@ export function memoriasTocadas(brain, caminhos) {
|
|
|
37
42
|
if (memoria.status !== "ativa")
|
|
38
43
|
continue;
|
|
39
44
|
for (const ancora of memoria.ancoras) {
|
|
40
|
-
const
|
|
45
|
+
const alvo = ancora.valor.split("#")[0];
|
|
46
|
+
const casa = caminhos.some((c) => c === alvo || c.startsWith(`${alvo}/`));
|
|
41
47
|
if (casa) {
|
|
42
48
|
tocadas.push({
|
|
43
49
|
pasta: memoria.pasta,
|
package/dist/cli/src/gravar.js
CHANGED
|
@@ -46,9 +46,18 @@ export function interpreta(texto) {
|
|
|
46
46
|
throw new Error(`frontmatter sem \`${chave}\`.`);
|
|
47
47
|
}
|
|
48
48
|
// O corpo vai ate a secao dos filtros; dali para baixo e metadado, nao conteudo.
|
|
49
|
-
const
|
|
50
|
-
|
|
49
|
+
const cabecalhoDosFiltros = /\n##\s+Os tr[êe]s filtros\s*\n/gi;
|
|
50
|
+
const ocorrencias = [...resto.matchAll(cabecalhoDosFiltros)];
|
|
51
|
+
if (ocorrencias.length === 0)
|
|
51
52
|
throw new Error("falta a seção `## Os três filtros`.");
|
|
53
|
+
// Duas ocorrencias tornam o arquivo ambiguo: cortar na primeira trunca o corpo (e os
|
|
54
|
+
// filtros ainda saem certos da segunda, entao grava e perde dado sem avisar), e cortar
|
|
55
|
+
// na ultima escolheria em silencio qual secao e a de verdade. Recusar diz o que houve.
|
|
56
|
+
if (ocorrencias.length > 1) {
|
|
57
|
+
throw new Error("a seção `## Os três filtros` aparece mais de uma vez — deixe só a real, " +
|
|
58
|
+
"ou reescreva a citação no corpo (por exemplo, entre crases).");
|
|
59
|
+
}
|
|
60
|
+
const corte = ocorrencias[0].index;
|
|
52
61
|
const corpo = resto
|
|
53
62
|
.slice(0, corte)
|
|
54
63
|
.replace(/<!--[\s\S]*?-->/g, "")
|
package/dist/cli/src/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { termosDaConsulta } from "./argv.js";
|
|
2
3
|
import { check, status } from "./check.js";
|
|
3
4
|
import { leConfigDoRepo, guardaToken } from "./config.js";
|
|
4
5
|
import { grava } from "./gravar.js";
|
|
5
6
|
import { init, SUGESTAO_MCP } from "./init.js";
|
|
6
7
|
import { LINHA_DE_IMPORT } from "./materializa.js";
|
|
8
|
+
import { buscaPolitica } from "./politica.js";
|
|
7
9
|
import { busca } from "./buscar.js";
|
|
8
10
|
import { arquiva, edita } from "./curar.js";
|
|
9
11
|
import { criaPasta } from "./pasta.js";
|
|
@@ -36,6 +38,9 @@ const AJUDA = `dd-harness — materializa política, briefing e Brain no reposit
|
|
|
36
38
|
dd-harness sync escreve os artefatos em disco
|
|
37
39
|
dd-harness check [--commit <sha>] mede as âncoras e reporta a deriva
|
|
38
40
|
dd-harness status só lê: o tamanho do Brain e o que espera julgamento
|
|
41
|
+
dd-harness politica imprime a política do serviço (para o hook de sessão)
|
|
42
|
+
saída 0 = veio; 3 = projeto sem política;
|
|
43
|
+
1 = não consegui buscar
|
|
39
44
|
dd-harness --help
|
|
40
45
|
|
|
41
46
|
O gerado vive em dd-harness/. Na raiz fica só o seu CLAUDE.md, que importa a
|
|
@@ -164,7 +169,7 @@ async function comandoArquivar(argv) {
|
|
|
164
169
|
console.log(" foi para o histórico, não foi apagada. Rode `dd-harness sync`.");
|
|
165
170
|
}
|
|
166
171
|
async function comandoBuscar(argv) {
|
|
167
|
-
const consulta = argv
|
|
172
|
+
const consulta = termosDaConsulta(argv);
|
|
168
173
|
if (!consulta)
|
|
169
174
|
throw new Error('uso: dd-harness buscar "<pergunta>"');
|
|
170
175
|
const limite = Number(argumento(argv, "limite")) || undefined;
|
|
@@ -261,6 +266,27 @@ async function comandoCheck(argv) {
|
|
|
261
266
|
}
|
|
262
267
|
}
|
|
263
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Imprime a politica no stdout, para o hook `SessionStart` injetar no contexto.
|
|
271
|
+
*
|
|
272
|
+
* Os codigos de saida sao o contrato com o hook, e existem para separar duas coisas que
|
|
273
|
+
* um "falhou" generico colapsaria: projeto novo (que precisa de briefing) de politica
|
|
274
|
+
* inalcancavel (que precisa PARAR a sessao). Mudar estes numeros quebra o hook.
|
|
275
|
+
*/
|
|
276
|
+
async function comandoPolitica() {
|
|
277
|
+
const r = await buscaPolitica(process.cwd());
|
|
278
|
+
if (r.estado === "ok") {
|
|
279
|
+
console.log(r.conteudo);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (r.estado === "sem-politica") {
|
|
283
|
+
console.error("Este projeto ainda não tem política — nunca foi briefado. Rode `/briefar`.");
|
|
284
|
+
process.exitCode = 3;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
console.error(`não consegui buscar a política: ${r.motivo}`);
|
|
288
|
+
process.exitCode = 1;
|
|
289
|
+
}
|
|
264
290
|
async function comandoStatus() {
|
|
265
291
|
const r = await status(process.cwd());
|
|
266
292
|
// O acervo vem primeiro, e sempre: e a resposta para "o que ha no Brain deste projeto?",
|
|
@@ -276,6 +302,14 @@ async function comandoStatus() {
|
|
|
276
302
|
(arquivadas ? ` e ${arquivadas} arquivada(s)` : "") +
|
|
277
303
|
(detalhe ? ` — ${detalhe}` : ""));
|
|
278
304
|
}
|
|
305
|
+
// Antes do early return abaixo: fila travada nao e "julgamento esperando", e sumiria
|
|
306
|
+
// justamente na sessao mais comum — a que nao tem deriva nem vencida.
|
|
307
|
+
if (r.travadasNaFila > 0) {
|
|
308
|
+
console.log("");
|
|
309
|
+
console.log(`AVISO: ${r.travadasNaFila} memória(s) desistiram de ser indexadas e não entram na busca semântica.`);
|
|
310
|
+
console.log(" O indexador tentou 5 vezes e parou. Rode `pnpm worker --reindexar`;");
|
|
311
|
+
console.log(" se repetir, o motivo está em `embedding_queue.ultimo_erro`.");
|
|
312
|
+
}
|
|
279
313
|
if (!r.comDeriva.length && !r.vencidas.length) {
|
|
280
314
|
console.log("Nada esperando julgamento.");
|
|
281
315
|
return;
|
|
@@ -366,6 +400,8 @@ async function principal() {
|
|
|
366
400
|
return comandoCheck(resto);
|
|
367
401
|
case "status":
|
|
368
402
|
return comandoStatus();
|
|
403
|
+
case "politica":
|
|
404
|
+
return comandoPolitica();
|
|
369
405
|
case "--help":
|
|
370
406
|
case "-h":
|
|
371
407
|
case undefined:
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { cabecalhos, credencial, pede } from "./api.js";
|
|
2
|
+
export async function buscaPolitica(raiz) {
|
|
3
|
+
let config;
|
|
4
|
+
try {
|
|
5
|
+
config = await credencial(raiz);
|
|
6
|
+
}
|
|
7
|
+
catch (erro) {
|
|
8
|
+
// Sem `.dd-harness.json` ou sem token nao da para nem perguntar. E inalcancavel, nao
|
|
9
|
+
// "sem politica": a politica pode muito bem existir do outro lado.
|
|
10
|
+
return { estado: "inalcancavel", motivo: mensagem(erro) };
|
|
11
|
+
}
|
|
12
|
+
const url = new URL(`${config.config.api}/api/v1/artefatos`);
|
|
13
|
+
url.searchParams.set("tenant", config.config.tenant);
|
|
14
|
+
url.searchParams.set("projeto", config.config.projeto);
|
|
15
|
+
try {
|
|
16
|
+
const resposta = await pede(url, { headers: cabecalhos(config.token) });
|
|
17
|
+
if (!resposta.ok) {
|
|
18
|
+
const { erro } = (await resposta.json().catch(() => ({})));
|
|
19
|
+
return {
|
|
20
|
+
estado: "inalcancavel",
|
|
21
|
+
motivo: erro ?? `a API respondeu ${resposta.status}.`,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const payload = (await resposta.json());
|
|
25
|
+
const conteudo = payload.politica?.trim();
|
|
26
|
+
// Vazio e nulo sao a mesma coisa aqui, e os dois significam "nunca foi escrita".
|
|
27
|
+
if (!conteudo)
|
|
28
|
+
return { estado: "sem-politica" };
|
|
29
|
+
return { estado: "ok", conteudo };
|
|
30
|
+
}
|
|
31
|
+
catch (erro) {
|
|
32
|
+
return { estado: "inalcancavel", motivo: mensagem(erro) };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const mensagem = (erro) => erro instanceof Error ? erro.message : String(erro);
|
package/package.json
CHANGED
|
@@ -1,41 +1,40 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dd-harness-mcp",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "Servidor MCP do dd-harness: o agente consulta e grava memoria como ferramenta, sem passar por arquivo.",
|
|
6
|
-
"license": "UNLICENSED",
|
|
7
|
-
"author": "Diego Dias",
|
|
8
|
-
"keywords": [
|
|
9
|
-
"claude-code",
|
|
10
|
-
"mcp",
|
|
11
|
-
"model-context-protocol",
|
|
12
|
-
"ai-agents",
|
|
13
|
-
"memory"
|
|
14
|
-
],
|
|
15
|
-
"homepage": "https://dd-harness.vercel.app",
|
|
16
|
-
"repository": {
|
|
17
|
-
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/diegodias93/dd-harness-online.git",
|
|
19
|
-
"directory": "packages/mcp"
|
|
20
|
-
},
|
|
21
|
-
"//bin": "`dist/mcp/src/` e nao `dist/` porque o build abrange dois diretorios (este pacote e o fonte do CLI), e o `rootDir` comum e `packages/`. Conferir com `node dist/mcp/src/index.js` antes de publicar: caminho errado aqui instala e quebra no primeiro uso.",
|
|
22
|
-
"bin": {
|
|
23
|
-
"dd-harness-mcp": "dist/mcp/src/index.js"
|
|
24
|
-
},
|
|
25
|
-
"files": [
|
|
26
|
-
"dist"
|
|
27
|
-
],
|
|
28
|
-
"engines": {
|
|
29
|
-
"node": ">=20"
|
|
30
|
-
},
|
|
31
|
-
"dependencies": {
|
|
32
|
-
"@modelcontextprotocol/server": "^2.0.0",
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "dd-harness-mcp",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Servidor MCP do dd-harness: o agente consulta e grava memoria como ferramenta, sem passar por arquivo.",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"author": "Diego Dias",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"claude-code",
|
|
10
|
+
"mcp",
|
|
11
|
+
"model-context-protocol",
|
|
12
|
+
"ai-agents",
|
|
13
|
+
"memory"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://dd-harness.vercel.app",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/diegodias93/dd-harness-online.git",
|
|
19
|
+
"directory": "packages/mcp"
|
|
20
|
+
},
|
|
21
|
+
"//bin": "`dist/mcp/src/` e nao `dist/` porque o build abrange dois diretorios (este pacote e o fonte do CLI), e o `rootDir` comum e `packages/`. Conferir com `node dist/mcp/src/index.js` antes de publicar: caminho errado aqui instala e quebra no primeiro uso.",
|
|
22
|
+
"bin": {
|
|
23
|
+
"dd-harness-mcp": "dist/mcp/src/index.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@modelcontextprotocol/server": "^2.0.0",
|
|
33
|
+
"zod": "^4.5.4"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"typecheck": "tsc -p . --noEmit",
|
|
37
|
+
"build": "tsc -p tsconfig.build.json",
|
|
38
|
+
"prepublishOnly": "npm run build"
|
|
39
|
+
}
|
|
40
|
+
}
|