dd-harness-mcp 0.8.0 → 0.10.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/escreve-config.js +25 -16
- package/dist/cli/src/index.js +185 -63
- package/dist/cli/src/politica.js +2 -1
- package/dist/cli/src/roadmap.js +153 -0
- package/dist/mcp/src/index.js +142 -1
- package/package.json +40 -40
|
@@ -63,13 +63,20 @@ export async function escreveMcp(raiz) {
|
|
|
63
63
|
return { ok: true, estado: eraVazio ? "criado" : "acrescentado" };
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
66
|
+
* Os dois pontos de entrada do repositorio — `CLAUDE.md` (Claude Code) e `AGENTS.md`
|
|
67
|
+
* (Codex, Cursor, Gemini CLI, Windsurf) — sao ESPELHOS: o mesmo apontamento para o
|
|
68
|
+
* dd-harness, sem regra propria. Nenhum dos dois e opcional, e nao ha pergunta sobre
|
|
69
|
+
* "qual ferramenta voce usa": quem abre o repositorio encontra o protocolo pela porta
|
|
70
|
+
* que a SUA ferramenta le, e as duas dizem a mesma coisa. Regra fica no servico; aqui
|
|
71
|
+
* fica so o ponteiro — por isso espelhar nao duplica nada que possa divergir.
|
|
72
|
+
*
|
|
73
|
+
* Markdown e livre, nao JSON: nao ha "mesclar" de verdade. Se o arquivo ja existe (com
|
|
74
|
+
* qualquer conteudo), so acrescenta a instrucao no fim; nunca sobrescreve o que a pessoa
|
|
75
|
+
* escreveu, porque markdown alheio e mais dificil de separar do nosso do que uma chave
|
|
76
|
+
* de objeto.
|
|
70
77
|
*/
|
|
71
|
-
export async function
|
|
72
|
-
const caminho = join(raiz,
|
|
78
|
+
export async function escrevePonteiro(raiz, arquivo) {
|
|
79
|
+
const caminho = join(raiz, arquivo);
|
|
73
80
|
const instrucao = SUGESTAO_AGENTS;
|
|
74
81
|
let atual = null;
|
|
75
82
|
try {
|
|
@@ -88,14 +95,16 @@ export async function escreveAgents(raiz) {
|
|
|
88
95
|
await writeFile(caminho, `${atual}${separador}${instrucao}\n`, "utf8");
|
|
89
96
|
return { estado: "acrescentado" };
|
|
90
97
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
de
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
/** Compatibilidade: `init` ainda chama pelo nome antigo. */
|
|
99
|
+
export const escreveAgents = (raiz) => escrevePonteiro(raiz, "AGENTS.md");
|
|
100
|
+
const SUGESTAO_AGENTS = `## Protocolo do dd-harness
|
|
101
|
+
|
|
102
|
+
Este projeto tem um protocolo de trabalho que **não está neste arquivo**.
|
|
103
|
+
|
|
104
|
+
**ANTES de qualquer outra coisa**, chame a ferramenta MCP \`ler_artefato\` com
|
|
105
|
+
\`tipo: "politica"\` e siga o que ela devolver. É a primeira ação da sessão, antes
|
|
106
|
+
de ler código, responder ou planejar.
|
|
107
|
+
|
|
108
|
+
- Se a ferramenta não existir, o servidor MCP do dd-harness não está declarado:
|
|
109
|
+
avise o usuário e **não modifique nada** até ele resolver.
|
|
101
110
|
- Se ela devolver vazio, este projeto nunca foi briefado — diga isso ao usuário.`;
|
package/dist/cli/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import { access } from "node:fs/promises";
|
|
3
|
+
import { access, readFile } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
// DEP0190: Node imprime esse warning direto no stderr no instante em que
|
|
6
6
|
// `spawn(..., { shell: true })` recebe um array de args — de forma sincrona, ANTES de
|
|
@@ -14,11 +14,12 @@ process.removeAllListeners("warning");
|
|
|
14
14
|
import { termosDaConsulta } from "./argv.js";
|
|
15
15
|
import { check, status } from "./check.js";
|
|
16
16
|
import { CAMINHO_CONFIG, guardaConfigDaMaquina, guardaToken, leConfigDaMaquina, leConfigDoRepo, leToken, } from "./config.js";
|
|
17
|
-
import {
|
|
17
|
+
import { escreveHook, escreveMcp, escrevePonteiro } from "./escreve-config.js";
|
|
18
18
|
import { grava } from "./gravar.js";
|
|
19
19
|
import { init, SUGESTAO_AGENTS, SUGESTAO_HOOK, SUGESTAO_MCP } from "./init.js";
|
|
20
20
|
import { pergunta, escolha, fechaPerguntas } from "./pergunta.js";
|
|
21
21
|
import { buscaPolitica } from "./politica.js";
|
|
22
|
+
import { blocoDeSessao, criaFase, editaFase, formataChangelog, formataRoadmap, leFases, } from "./roadmap.js";
|
|
22
23
|
import { busca } from "./buscar.js";
|
|
23
24
|
import { arquiva, edita, le } from "./curar.js";
|
|
24
25
|
import { criaPasta } from "./pasta.js";
|
|
@@ -33,61 +34,142 @@ import { subiuOWorker } from "./worker.js";
|
|
|
33
34
|
* em repositorio alheio tem que envelhecer bem, e cada dependencia e uma chance de nao
|
|
34
35
|
* envelhecer.
|
|
35
36
|
*/
|
|
36
|
-
const AJUDA = `dd-harness — a política e o Brain do projeto, no serviço
|
|
37
|
-
|
|
38
|
-
dd-harness start numa pasta vazia: conduz tudo (login,
|
|
39
|
-
espaço, projeto, config) numa tacada
|
|
40
|
-
dd-harness login --token <token> [--api <url>]
|
|
41
|
-
guarda a credencial desta máquina
|
|
42
|
-
dd-harness projeto <slug> --nome "<nome>" [--tenant <t>] [--api <url>]
|
|
43
|
-
cria o projeto no serviço (antes do init)
|
|
44
|
-
dd-harness init --tenant <t> --projeto <p> [--api <url>]
|
|
45
|
-
prepara o repositório (config + CLAUDE.md)
|
|
46
|
-
dd-harness pasta <slug> --definicao "o que entra e o que não entra"
|
|
47
|
-
cria a pasta que o gravar exige
|
|
48
|
-
dd-harness gravar <arquivo.md> registra uma memória a partir de um markdown
|
|
49
|
-
dd-harness editar <arquivo.md> corrige o que já está gravado
|
|
50
|
-
dd-harness arquivar <pasta>/<slug> --motivo <obsoleta|incorreta|fora_dos_filtros>
|
|
51
|
-
[--substituida-por <pasta>/<slug>]
|
|
52
|
-
tira de circulação sem apagar
|
|
53
|
-
dd-harness buscar "<pergunta>" acha memória por relevância, não por arquivo
|
|
54
|
-
dd-harness ler <pasta>/<slug> imprime a memória inteira, no formato de gravar
|
|
55
|
-
dd-harness reancorar <pasta>/<slug> --de "<alvo>" --para "<alvo>"
|
|
56
|
-
troca o alvo de uma âncora que mudou de lugar
|
|
57
|
-
dd-harness check [--commit <sha>] mede as âncoras e reporta a deriva
|
|
58
|
-
dd-harness status só lê: o tamanho do Brain e o que espera julgamento
|
|
59
|
-
dd-harness politica [--hook] imprime a política do serviço
|
|
60
|
-
saída 0 = veio; 3 = projeto sem política;
|
|
61
|
-
1 = não consegui buscar
|
|
62
|
-
--hook: fala o protocolo do SessionStart do
|
|
63
|
-
Claude Code, para pôr a política no contexto
|
|
64
|
-
dd-harness
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
37
|
+
const AJUDA = `dd-harness — a política e o Brain do projeto, no serviço
|
|
38
|
+
|
|
39
|
+
dd-harness start numa pasta vazia: conduz tudo (login,
|
|
40
|
+
espaço, projeto, config) numa tacada
|
|
41
|
+
dd-harness login --token <token> [--api <url>]
|
|
42
|
+
guarda a credencial desta máquina
|
|
43
|
+
dd-harness projeto <slug> --nome "<nome>" [--tenant <t>] [--api <url>]
|
|
44
|
+
cria o projeto no serviço (antes do init)
|
|
45
|
+
dd-harness init --tenant <t> --projeto <p> [--api <url>]
|
|
46
|
+
prepara o repositório (config + CLAUDE.md)
|
|
47
|
+
dd-harness pasta <slug> --definicao "o que entra e o que não entra"
|
|
48
|
+
cria a pasta que o gravar exige
|
|
49
|
+
dd-harness gravar <arquivo.md> registra uma memória a partir de um markdown
|
|
50
|
+
dd-harness editar <arquivo.md> corrige o que já está gravado
|
|
51
|
+
dd-harness arquivar <pasta>/<slug> --motivo <obsoleta|incorreta|fora_dos_filtros>
|
|
52
|
+
[--substituida-por <pasta>/<slug>]
|
|
53
|
+
tira de circulação sem apagar
|
|
54
|
+
dd-harness buscar "<pergunta>" acha memória por relevância, não por arquivo
|
|
55
|
+
dd-harness ler <pasta>/<slug> imprime a memória inteira, no formato de gravar
|
|
56
|
+
dd-harness reancorar <pasta>/<slug> --de "<alvo>" --para "<alvo>"
|
|
57
|
+
troca o alvo de uma âncora que mudou de lugar
|
|
58
|
+
dd-harness check [--commit <sha>] mede as âncoras e reporta a deriva
|
|
59
|
+
dd-harness status só lê: o tamanho do Brain e o que espera julgamento
|
|
60
|
+
dd-harness politica [--hook] imprime a política do serviço
|
|
61
|
+
saída 0 = veio; 3 = projeto sem política;
|
|
62
|
+
1 = não consegui buscar
|
|
63
|
+
--hook: fala o protocolo do SessionStart do
|
|
64
|
+
Claude Code, para pôr a política no contexto
|
|
65
|
+
dd-harness roadmap as fases abertas: a atual inteira, as próximas
|
|
66
|
+
por título (opcional — projeto sem fase não tem)
|
|
67
|
+
dd-harness changelog [--versao <v>]
|
|
68
|
+
o que já foi concluído, agrupado por versão
|
|
69
|
+
dd-harness fase criar --titulo "<t>" [--conteudo <arquivo.md>] [--versao <v>]
|
|
70
|
+
[--status ideia|aberta|concluida] [--slug <s>]
|
|
71
|
+
dd-harness fase editar <slug> [--titulo "<t>"] [--conteudo <arquivo.md>]
|
|
72
|
+
[--versao <v> | --sem-versao] [--status <s>] [--ordem <n>]
|
|
73
|
+
concluir = --status concluida: a fase sai do
|
|
74
|
+
roadmap e entra no changelog, nada migra
|
|
75
|
+
dd-harness --help
|
|
76
|
+
dd-harness --version qual binário está instalado nesta máquina
|
|
77
|
+
|
|
78
|
+
Nada do dd-harness fica em disco: a política chega pelo hook de sessão, e a
|
|
79
|
+
memória pela busca, na hora.
|
|
69
80
|
`;
|
|
70
81
|
/**
|
|
71
82
|
* Os ganchos sao IMPRESSOS, nunca instalados. `.git/hooks` nao e versionado e nao e
|
|
72
83
|
* nosso: escrever la dentro sem a pessoa pedir e o mesmo tipo de invasao que
|
|
73
84
|
* sobrescrever o CLAUDE.md dela. Quem cola, decide.
|
|
74
85
|
*/
|
|
75
|
-
const GANCHOS = `
|
|
76
|
-
Opcional — o gancho que devolve a memória ao code review:
|
|
77
|
-
|
|
78
|
-
.git/hooks/post-commit (avisa quais memórias falam do que você mudou)
|
|
79
|
-
#!/bin/sh
|
|
80
|
-
dd-harness check --commit "$(git rev-parse HEAD)" || true
|
|
81
|
-
|
|
82
|
-
Termina em sucesso mesmo com deriva: avisa, não bloqueia.
|
|
83
|
-
|
|
84
|
-
O hook da política (\`dd-harness politica --hook\`) é outra coisa, e não é
|
|
86
|
+
const GANCHOS = `
|
|
87
|
+
Opcional — o gancho que devolve a memória ao code review:
|
|
88
|
+
|
|
89
|
+
.git/hooks/post-commit (avisa quais memórias falam do que você mudou)
|
|
90
|
+
#!/bin/sh
|
|
91
|
+
dd-harness check --commit "$(git rev-parse HEAD)" || true
|
|
92
|
+
|
|
93
|
+
Termina em sucesso mesmo com deriva: avisa, não bloqueia.
|
|
94
|
+
|
|
95
|
+
O hook da política (\`dd-harness politica --hook\`) é outra coisa, e não é
|
|
85
96
|
opcional — \`dd-harness init\` imprime a linha para o \`.claude/settings.json\`.`;
|
|
86
97
|
function argumento(argv, nome) {
|
|
87
98
|
const i = argv.indexOf(`--${nome}`);
|
|
88
99
|
return i >= 0 ? argv[i + 1] : undefined;
|
|
89
100
|
}
|
|
90
101
|
const API_PADRAO = "https://dd-harness.vercel.app";
|
|
102
|
+
// --- roadmap e changelog ---
|
|
103
|
+
async function comandoRoadmap() {
|
|
104
|
+
console.log(formataRoadmap(await leFases(process.cwd())));
|
|
105
|
+
}
|
|
106
|
+
async function comandoChangelog(argv) {
|
|
107
|
+
const versao = argumento(argv, "versao");
|
|
108
|
+
const fases = await leFases(process.cwd(), { status: "concluida", ...(versao ? { versao } : {}) });
|
|
109
|
+
console.log(formataChangelog(fases));
|
|
110
|
+
}
|
|
111
|
+
const STATUS_DE_FASE = ["ideia", "aberta", "concluida", "descartada"];
|
|
112
|
+
function statusDoArgv(argv) {
|
|
113
|
+
const s = argumento(argv, "status");
|
|
114
|
+
if (s === undefined)
|
|
115
|
+
return undefined;
|
|
116
|
+
if (!STATUS_DE_FASE.includes(s)) {
|
|
117
|
+
throw new Error(`--status: use ${STATUS_DE_FASE.join(", ")}.`);
|
|
118
|
+
}
|
|
119
|
+
return s;
|
|
120
|
+
}
|
|
121
|
+
/** `--conteudo` aponta para um .md, como `gravar` e `editar` — markdown de verdade nao cabe em flag. */
|
|
122
|
+
async function conteudoDoArgv(argv) {
|
|
123
|
+
const caminho = argumento(argv, "conteudo");
|
|
124
|
+
return caminho === undefined ? undefined : readFile(caminho, "utf8");
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* `fase criar` e `fase editar`. Concluir uma fase e `fase editar <slug> --status concluida`
|
|
128
|
+
* — nao ha verbo proprio de proposito: e uma edicao de status como qualquer outra, e e o
|
|
129
|
+
* banco que a transforma em changelog.
|
|
130
|
+
*/
|
|
131
|
+
async function comandoFase(argv) {
|
|
132
|
+
const [acao, ...resto] = argv;
|
|
133
|
+
if (acao === "criar") {
|
|
134
|
+
const titulo = argumento(resto, "titulo");
|
|
135
|
+
if (!titulo)
|
|
136
|
+
throw new Error('fase criar exige --titulo "<título>".');
|
|
137
|
+
const status = statusDoArgv(resto);
|
|
138
|
+
if (status === "descartada")
|
|
139
|
+
throw new Error("não se cria uma fase já descartada.");
|
|
140
|
+
const { fase } = await criaFase(process.cwd(), {
|
|
141
|
+
titulo,
|
|
142
|
+
conteudo: await conteudoDoArgv(resto),
|
|
143
|
+
versao: argumento(resto, "versao"),
|
|
144
|
+
status,
|
|
145
|
+
slug: argumento(resto, "slug"),
|
|
146
|
+
});
|
|
147
|
+
console.log(`Fase ${fase.slug} criada — ordem ${fase.ordem}, ${fase.status}` +
|
|
148
|
+
(fase.versao ? `, versão ${fase.versao}` : "") +
|
|
149
|
+
".");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (acao === "editar") {
|
|
153
|
+
const [slug, ...opcoes] = resto;
|
|
154
|
+
if (!slug || slug.startsWith("--"))
|
|
155
|
+
throw new Error("fase editar exige o slug da fase.");
|
|
156
|
+
const ordemBruta = argumento(opcoes, "ordem");
|
|
157
|
+
const status = statusDoArgv(opcoes);
|
|
158
|
+
const { fase } = await editaFase(process.cwd(), slug, {
|
|
159
|
+
titulo: argumento(opcoes, "titulo"),
|
|
160
|
+
conteudo: await conteudoDoArgv(opcoes),
|
|
161
|
+
versao: opcoes.includes("--sem-versao") ? null : argumento(opcoes, "versao"),
|
|
162
|
+
status,
|
|
163
|
+
ordem: ordemBruta === undefined ? undefined : Number(ordemBruta),
|
|
164
|
+
});
|
|
165
|
+
console.log(`Fase ${fase.slug} atualizada — ${fase.status}` +
|
|
166
|
+
(fase.versao ? `, versão ${fase.versao}` : "") +
|
|
167
|
+
(status === "concluida" ? " (saiu do roadmap, entrou no changelog)" : "") +
|
|
168
|
+
".");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
throw new Error('uso: dd-harness fase criar --titulo "<t>" [...] | dd-harness fase editar <slug> [...]');
|
|
172
|
+
}
|
|
91
173
|
/**
|
|
92
174
|
* Forca o `npx` a baixar e cachear `dd-harness-mcp` AGORA, dentro do wizard — nunca na
|
|
93
175
|
* primeira conexao do Claude Code. Sem isto, a primeira instalacao acontecia so quando
|
|
@@ -151,6 +233,10 @@ function tentaAbrirNavegador(url) {
|
|
|
151
233
|
// Segue sem navegador — a URL impressa antes desta chamada ja resolve.
|
|
152
234
|
}
|
|
153
235
|
}
|
|
236
|
+
/** Token no terminal aparece mascarado: o prefixo basta para reconhecer, o segredo nao precisa ser lido em voz alta. */
|
|
237
|
+
function mascara(token) {
|
|
238
|
+
return token.length <= 16 ? "***" : `${token.slice(0, 12)}...${token.slice(-4)}`;
|
|
239
|
+
}
|
|
154
240
|
/**
|
|
155
241
|
* `dd-harness start` — o wizard para pasta vazia.
|
|
156
242
|
*
|
|
@@ -185,13 +271,27 @@ async function comandoStart() {
|
|
|
185
271
|
"novo — start segue mesmo assim, mas pode faltar correção.\n");
|
|
186
272
|
}
|
|
187
273
|
// 1. Credencial. Sem ela nada do resto e possivel — nem listar tenant.
|
|
274
|
+
// O estado aparece SEMPRE, mesmo quando ja existe credencial: antes o passo era
|
|
275
|
+
// pulado em silencio, e quem rodava `start` com um token velho (ou de outra conta)
|
|
276
|
+
// nao tinha como perceber — o wizard so falhava depois, ao listar espacos.
|
|
188
277
|
let token = await leToken(API_PADRAO);
|
|
278
|
+
if (token) {
|
|
279
|
+
console.log(`Credencial: ${mascara(token)} (${API_PADRAO})`);
|
|
280
|
+
const troca = await pergunta("Usar esta credencial? [S/n]: ");
|
|
281
|
+
if (/^n/i.test(troca))
|
|
282
|
+
token = null;
|
|
283
|
+
}
|
|
189
284
|
if (!token) {
|
|
190
|
-
|
|
191
|
-
|
|
285
|
+
const url = `${API_PADRAO}/tokens`;
|
|
286
|
+
console.log(`\nCrie um token pessoal em ${url} — abrindo no navegador...`);
|
|
287
|
+
tentaAbrirNavegador(url);
|
|
288
|
+
token = await pergunta("Cole o token aqui: ");
|
|
289
|
+
if (!token)
|
|
290
|
+
throw new Error("sem token não dá para seguir: `start` fala com o serviço em todos os passos.");
|
|
192
291
|
await guardaToken(API_PADRAO, token);
|
|
193
|
-
console.log("Credencial guardada
|
|
292
|
+
console.log("Credencial guardada.");
|
|
194
293
|
}
|
|
294
|
+
console.log("");
|
|
195
295
|
// 2. Espaco (tenant). Criar um e ato de dono — nunca por aqui — mas ESCOLHER entre
|
|
196
296
|
// os que ja existem e so leitura, e e o que faltava para nao exigir o slug de memoria.
|
|
197
297
|
let tenants = await listaTenants(API_PADRAO, token);
|
|
@@ -203,18 +303,28 @@ async function comandoStart() {
|
|
|
203
303
|
tenants = await listaTenants(API_PADRAO, token);
|
|
204
304
|
}
|
|
205
305
|
const tenant = await escolha("\nEscolha um espaço:", tenants.map((t) => ({ rotulo: `${t.nome} (${t.slug})`, valor: t.slug })));
|
|
206
|
-
// 3. Projeto. Criar e subordinado a estar num espaco
|
|
207
|
-
|
|
306
|
+
// 3. Projeto. Criar e subordinado a estar num espaco - isso o CLI ja faz sozinho.
|
|
307
|
+
// A pasta se chama `tiktok-drawer`; o projeto se chama `TikTok Drawer`. Sugerir o
|
|
308
|
+
// nome da pasta cru fazia o wizard propor um identificador onde se pede um titulo —
|
|
309
|
+
// e o slug, que e quem precisa daquele formato, ja sai daqui de baixo sozinho.
|
|
310
|
+
const pasta = process.cwd().split(/[\\/]/).pop() ?? "meu-projeto";
|
|
311
|
+
const nomeSugerido = pasta
|
|
312
|
+
.replace(/[-_]+/g, " ")
|
|
313
|
+
.replace(/\s+/g, " ")
|
|
314
|
+
.trim()
|
|
315
|
+
.replace(/\b\w/g, (c) => c.toUpperCase()) || "Meu Projeto";
|
|
208
316
|
const nome = (await pergunta(`\nNome do projeto [${nomeSugerido}]: `)) || nomeSugerido;
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
|
|
317
|
+
// O slug sai do nome, sem perguntar: e identificador, nao escolha editorial, e quem
|
|
318
|
+
// esta comecando nao tem como saber que ele vira a chave do `.dd-harness.json`.
|
|
319
|
+
// Nome so com caracteres especiais (ex: "!!!") normaliza para string vazia - o
|
|
320
|
+
// fallback evita slug vazio, que o servidor recusaria com erro confuso.
|
|
321
|
+
const slugDoProjeto = nome
|
|
212
322
|
.toLowerCase()
|
|
213
323
|
.normalize("NFD")
|
|
214
324
|
.replace(/[̀-ͯ]/g, "")
|
|
215
325
|
.replace(/[^a-z0-9]+/g, "-")
|
|
216
326
|
.replace(/^-+|-+$/g, "") || "meu-projeto";
|
|
217
|
-
|
|
327
|
+
console.log(`Identificador: ${slugDoProjeto}`);
|
|
218
328
|
const resultadoProjeto = await criaProjeto(process.cwd(), slugDoProjeto, nome, {
|
|
219
329
|
tenant,
|
|
220
330
|
api: API_PADRAO,
|
|
@@ -259,15 +369,18 @@ async function comandoStart() {
|
|
|
259
369
|
"conexão do Claude Code pode demorar ou falhar; se falhar, abra uma nova sessão " +
|
|
260
370
|
"e tente de novo.");
|
|
261
371
|
}
|
|
262
|
-
// 6. AGENTS.md —
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
372
|
+
// 6. CLAUDE.md e AGENTS.md — os dois SEMPRE, sem perguntar. Sao espelhos: o mesmo
|
|
373
|
+
// apontamento para o servico, cada um na porta que uma familia de ferramenta le. A
|
|
374
|
+
// pergunta "usa outra ferramenta?" saiu porque a resposta nao muda nada que valha a
|
|
375
|
+
// pena — quem responde "nao" hoje abre o repositorio no Cursor semana que vem e nao
|
|
376
|
+
// encontra protocolo nenhum, e o custo de ja ter o arquivo e um ponteiro de 8 linhas.
|
|
377
|
+
console.log("");
|
|
378
|
+
for (const arquivo of ["CLAUDE.md", "AGENTS.md"]) {
|
|
379
|
+
const r = await escrevePonteiro(process.cwd(), arquivo);
|
|
380
|
+
console.log({ criado: `criado ${arquivo}`,
|
|
381
|
+
"ja-tinha": `mantido ${arquivo} — já apontava para a política`,
|
|
382
|
+
acrescentado: `atualizado ${arquivo} (apontamento acrescentado ao que já existia)`,
|
|
383
|
+
}[r.estado]);
|
|
271
384
|
}
|
|
272
385
|
// 7. Onde esta o monorepo do worker nesta maquina? So pergunta uma vez, e so importa
|
|
273
386
|
// se houver memoria na fila agora — pular aqui nao trava nada, so avisa mais vezes.
|
|
@@ -557,6 +670,9 @@ function contextoDaSessao(r, worker) {
|
|
|
557
670
|
additionalContext: "# Política deste projeto (carregada do dd-harness)\n\n" +
|
|
558
671
|
"As regras abaixo valem para esta sessão inteira.\n\n" +
|
|
559
672
|
r.conteudo +
|
|
673
|
+
// Depois da politica, antes dos avisos operacionais: o roadmap e contexto de
|
|
674
|
+
// trabalho, a fila e ruido de infraestrutura. Vazio quando nao ha fase aberta.
|
|
675
|
+
blocoDeSessao(r.roadmap) +
|
|
560
676
|
fila,
|
|
561
677
|
};
|
|
562
678
|
}
|
|
@@ -771,6 +887,12 @@ async function principal() {
|
|
|
771
887
|
return comandoStatus();
|
|
772
888
|
case "politica":
|
|
773
889
|
return comandoPolitica(resto);
|
|
890
|
+
case "roadmap":
|
|
891
|
+
return comandoRoadmap();
|
|
892
|
+
case "changelog":
|
|
893
|
+
return comandoChangelog(resto);
|
|
894
|
+
case "fase":
|
|
895
|
+
return comandoFase(resto);
|
|
774
896
|
case "--version":
|
|
775
897
|
case "-V":
|
|
776
898
|
case "version":
|
package/dist/cli/src/politica.js
CHANGED
|
@@ -24,6 +24,7 @@ export async function buscaPolitica(raiz) {
|
|
|
24
24
|
const payload = (await resposta.json());
|
|
25
25
|
const conteudo = payload.politica?.trim();
|
|
26
26
|
const esperandoIndexacao = payload.esperando_indexacao ?? 0;
|
|
27
|
+
const roadmap = payload.roadmap;
|
|
27
28
|
// `briefado` e do SERVICO, nao inferido aqui: exige politica E briefing, os dois
|
|
28
29
|
// obrigatorios. So checar `politica` vazia deixava passar o caso de politica
|
|
29
30
|
// existir sem briefing — a sessao seguia como "ok" com metade do briefing faltando,
|
|
@@ -31,7 +32,7 @@ export async function buscaPolitica(raiz) {
|
|
|
31
32
|
if (!conteudo || payload.briefado === false) {
|
|
32
33
|
return { estado: "sem-politica", esperandoIndexacao };
|
|
33
34
|
}
|
|
34
|
-
return { estado: "ok", conteudo, esperandoIndexacao };
|
|
35
|
+
return { estado: "ok", conteudo, esperandoIndexacao, roadmap };
|
|
35
36
|
}
|
|
36
37
|
catch (erro) {
|
|
37
38
|
return { estado: "inalcancavel", motivo: mensagem(erro) };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { cabecalhos, credencial, pede, recusa } from "./api.js";
|
|
2
|
+
// --- rede ---
|
|
3
|
+
export async function leFases(raiz, filtro = {}) {
|
|
4
|
+
const { config, token } = await credencial(raiz);
|
|
5
|
+
const url = new URL(`${config.api}/api/v1/roadmap`);
|
|
6
|
+
url.searchParams.set("tenant", config.tenant);
|
|
7
|
+
url.searchParams.set("projeto", config.projeto);
|
|
8
|
+
if (filtro.status)
|
|
9
|
+
url.searchParams.set("status", filtro.status);
|
|
10
|
+
if (filtro.versao)
|
|
11
|
+
url.searchParams.set("versao", filtro.versao);
|
|
12
|
+
const resposta = await pede(url, { headers: cabecalhos(token) });
|
|
13
|
+
if (!resposta.ok)
|
|
14
|
+
await recusa(resposta);
|
|
15
|
+
return (await resposta.json()).fases;
|
|
16
|
+
}
|
|
17
|
+
export async function leFase(raiz, slug) {
|
|
18
|
+
const { config, token } = await credencial(raiz);
|
|
19
|
+
const url = new URL(`${config.api}/api/v1/roadmap/${encodeURIComponent(slug)}`);
|
|
20
|
+
url.searchParams.set("tenant", config.tenant);
|
|
21
|
+
url.searchParams.set("projeto", config.projeto);
|
|
22
|
+
const resposta = await pede(url, { headers: cabecalhos(token) });
|
|
23
|
+
if (!resposta.ok)
|
|
24
|
+
await recusa(resposta);
|
|
25
|
+
return (await resposta.json()).fase;
|
|
26
|
+
}
|
|
27
|
+
export async function criaFase(raiz, dados) {
|
|
28
|
+
const { config, token } = await credencial(raiz);
|
|
29
|
+
const resposta = await pede(`${config.api}/api/v1/roadmap`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: cabecalhos(token, true),
|
|
32
|
+
body: JSON.stringify({ tenant: config.tenant, projeto: config.projeto, ...dados }),
|
|
33
|
+
});
|
|
34
|
+
if (!resposta.ok)
|
|
35
|
+
await recusa(resposta);
|
|
36
|
+
return (await resposta.json());
|
|
37
|
+
}
|
|
38
|
+
export async function editaFase(raiz, slug, dados) {
|
|
39
|
+
const { config, token } = await credencial(raiz);
|
|
40
|
+
const resposta = await pede(`${config.api}/api/v1/roadmap/${encodeURIComponent(slug)}`, {
|
|
41
|
+
method: "PATCH",
|
|
42
|
+
headers: cabecalhos(token, true),
|
|
43
|
+
body: JSON.stringify({ tenant: config.tenant, projeto: config.projeto, ...dados }),
|
|
44
|
+
});
|
|
45
|
+
if (!resposta.ok)
|
|
46
|
+
await recusa(resposta);
|
|
47
|
+
return (await resposta.json());
|
|
48
|
+
}
|
|
49
|
+
// --- formatacao (pura) ---
|
|
50
|
+
/** Rotulo do grupo de fases concluidas sem versao: o "Unreleased" do Keep a Changelog. */
|
|
51
|
+
export const SEM_VERSAO = "Sem versão (ainda não lançado)";
|
|
52
|
+
const rotuloDeVersao = (versao) => (versao ? ` (${versao})` : "");
|
|
53
|
+
/**
|
|
54
|
+
* O bloco que entra no contexto da sessao, colado depois da politica. Vazio quando o
|
|
55
|
+
* projeto nao tem fase aberta: aviso sem motivo em toda sessao e o que ensina a ignorar
|
|
56
|
+
* aviso — e roadmap e opcional.
|
|
57
|
+
*
|
|
58
|
+
* So a fase atual vai em texto integral. As proximas vao por titulo, e as concluidas nem
|
|
59
|
+
* isso — so a contagem. E a reducao de tokens que o modelo file-based nao tinha como
|
|
60
|
+
* fazer: la o arquivo entrava inteiro, feito e por fazer.
|
|
61
|
+
*/
|
|
62
|
+
export function blocoDeSessao(r) {
|
|
63
|
+
if (!r?.agora)
|
|
64
|
+
return "";
|
|
65
|
+
const linhas = [
|
|
66
|
+
"",
|
|
67
|
+
"",
|
|
68
|
+
"---",
|
|
69
|
+
"",
|
|
70
|
+
"# Roadmap deste projeto (dd-harness)",
|
|
71
|
+
"",
|
|
72
|
+
`## Agora — ${r.agora.titulo}${rotuloDeVersao(r.agora.versao)}`,
|
|
73
|
+
"",
|
|
74
|
+
r.agora.conteudo.trim() || "_(fase sem descrição — pergunte ao usuário o que ela cobre.)_",
|
|
75
|
+
];
|
|
76
|
+
if (r.a_seguir.length > 0) {
|
|
77
|
+
linhas.push("", "## A seguir");
|
|
78
|
+
for (const f of r.a_seguir)
|
|
79
|
+
linhas.push(`- ${f.titulo}${rotuloDeVersao(f.versao)}`);
|
|
80
|
+
}
|
|
81
|
+
linhas.push("", r.concluidas > 0
|
|
82
|
+
? `${r.concluidas} fase(s) já concluída(s) — \`ler_changelog\` mostra o que foi entregue.`
|
|
83
|
+
: "Nenhuma fase concluída ainda.", "", "É da fase **Agora** que saem os passos desta sessão. Quando ela terminar, proponha ao " +
|
|
84
|
+
"usuário marcá-la concluída (`editar_fase` com `status: \"concluida\"`) — ela sai do " +
|
|
85
|
+
"roadmap e entra no changelog sozinha. O roadmap guarda o quê e quando; o porquê das " +
|
|
86
|
+
"decisões vai ao Brain, pelos três filtros, e fase concluída não gera memória por padrão.");
|
|
87
|
+
return linhas.join("\n");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* O roadmap para o terminal e para `ler_roadmap`: recebe TODAS as fases do projeto e
|
|
91
|
+
* separa por status. Concluidas nao aparecem aqui — sao changelog.
|
|
92
|
+
*/
|
|
93
|
+
export function formataRoadmap(fases) {
|
|
94
|
+
const porOrdem = (a, b) => a.ordem - b.ordem;
|
|
95
|
+
const abertas = fases.filter((f) => f.status === "aberta").sort(porOrdem);
|
|
96
|
+
const ideias = fases.filter((f) => f.status === "ideia").sort(porOrdem);
|
|
97
|
+
const concluidas = fases.filter((f) => f.status === "concluida").length;
|
|
98
|
+
if (abertas.length === 0 && ideias.length === 0) {
|
|
99
|
+
return ("Este projeto não tem roadmap." +
|
|
100
|
+
(concluidas > 0 ? ` (${concluidas} fase(s) concluída(s) no changelog.)` : "") +
|
|
101
|
+
"\nCrie a primeira fase: `dd-harness fase criar --titulo \"...\"`.");
|
|
102
|
+
}
|
|
103
|
+
const linhas = [];
|
|
104
|
+
const [agora, ...aSeguir] = abertas;
|
|
105
|
+
if (agora) {
|
|
106
|
+
linhas.push(`## Agora — ${agora.titulo}${rotuloDeVersao(agora.versao)} [${agora.slug}]`, "");
|
|
107
|
+
linhas.push(agora.conteudo.trim() || "_(sem descrição)_");
|
|
108
|
+
}
|
|
109
|
+
if (aSeguir.length > 0) {
|
|
110
|
+
linhas.push("", "## A seguir");
|
|
111
|
+
for (const f of aSeguir)
|
|
112
|
+
linhas.push(`- ${f.titulo}${rotuloDeVersao(f.versao)} [${f.slug}]`);
|
|
113
|
+
}
|
|
114
|
+
if (ideias.length > 0) {
|
|
115
|
+
linhas.push("", "## Ideias (não comprometidas)");
|
|
116
|
+
for (const f of ideias)
|
|
117
|
+
linhas.push(`- ${f.titulo}${rotuloDeVersao(f.versao)} [${f.slug}]`);
|
|
118
|
+
}
|
|
119
|
+
// Neutro de proposito: este texto sai igual no terminal e na ferramenta MCP, e cada
|
|
120
|
+
// porta tem o seu jeito de abrir o changelog.
|
|
121
|
+
linhas.push("", concluidas > 0
|
|
122
|
+
? `${concluidas} fase(s) concluída(s) — estão no changelog.`
|
|
123
|
+
: "Nenhuma fase concluída ainda.");
|
|
124
|
+
return linhas.join("\n");
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* O changelog: fases concluidas agrupadas por versao, na ordem em que a API devolveu (mais
|
|
128
|
+
* recente primeiro). Grupo sem versao recebe `SEM_VERSAO`. A ordem dos grupos e a da
|
|
129
|
+
* primeira aparicao — versao e rotulo livre, entao nao ha como ordena-la melhor que pelo
|
|
130
|
+
* tempo em que suas fases foram concluidas.
|
|
131
|
+
*/
|
|
132
|
+
export function formataChangelog(concluidas) {
|
|
133
|
+
if (concluidas.length === 0)
|
|
134
|
+
return "Nenhuma fase concluída ainda.";
|
|
135
|
+
const grupos = new Map();
|
|
136
|
+
for (const f of concluidas) {
|
|
137
|
+
const chave = f.versao ?? SEM_VERSAO;
|
|
138
|
+
const grupo = grupos.get(chave) ?? [];
|
|
139
|
+
grupo.push(f);
|
|
140
|
+
grupos.set(chave, grupo);
|
|
141
|
+
}
|
|
142
|
+
const linhas = [];
|
|
143
|
+
for (const [versao, fases] of grupos) {
|
|
144
|
+
if (linhas.length > 0)
|
|
145
|
+
linhas.push("");
|
|
146
|
+
linhas.push(`## ${versao}`);
|
|
147
|
+
for (const f of fases) {
|
|
148
|
+
const data = f.concluida_em ? f.concluida_em.slice(0, 10) : "";
|
|
149
|
+
linhas.push(`- ${f.titulo}${data ? ` — ${data}` : ""} [${f.slug}]`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return linhas.join("\n");
|
|
153
|
+
}
|
package/dist/mcp/src/index.js
CHANGED
|
@@ -12,6 +12,7 @@ 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
|
+
import { criaFase, editaFase, formataChangelog, formataRoadmap, leFases, } from "../../cli/src/roadmap.js";
|
|
15
16
|
/**
|
|
16
17
|
* O mesmo servico, outra porta.
|
|
17
18
|
*
|
|
@@ -292,6 +293,146 @@ Conteúdo vazio é válido e significa apagar o artefato.`,
|
|
|
292
293
|
return falha(erro);
|
|
293
294
|
}
|
|
294
295
|
});
|
|
296
|
+
// --- roadmap e changelog: o mesmo dado, duas leituras ---
|
|
297
|
+
server.registerTool("ler_roadmap", {
|
|
298
|
+
description: `As fases ABERTAS do projeto: a atual em texto integral (o "Agora" — um foco por vez) e as próximas por título (o "A seguir"). Opcionalmente as ideias não comprometidas.
|
|
299
|
+
|
|
300
|
+
A fase atual já chega no início da sessão, junto da política. Use isto quando precisar ver as próximas com mais detalhe, ou quando o roadmap tiver mudado durante a sessão.
|
|
301
|
+
|
|
302
|
+
Fase concluída NÃO aparece aqui — é changelog (\`ler_changelog\`). Roadmap é opcional: projeto sem fase devolve isso, e não é erro. Se o trabalho tiver fases de verdade e não houver roadmap, proponha criar a primeira com \`criar_fase\`.`,
|
|
303
|
+
inputSchema: z.object({
|
|
304
|
+
incluir_ideias: z
|
|
305
|
+
.boolean()
|
|
306
|
+
.optional()
|
|
307
|
+
.describe("Mostrar também as fases com status `ideia` (não comprometidas). Padrão: não."),
|
|
308
|
+
}),
|
|
309
|
+
}, async ({ incluir_ideias }) => {
|
|
310
|
+
try {
|
|
311
|
+
const fases = await leFases(raiz);
|
|
312
|
+
const visiveis = incluir_ideias ? fases : fases.filter((f) => f.status !== "ideia");
|
|
313
|
+
return texto(formataRoadmap(visiveis));
|
|
314
|
+
}
|
|
315
|
+
catch (erro) {
|
|
316
|
+
return falha(erro);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
server.registerTool("ler_changelog", {
|
|
320
|
+
description: `O que já foi ENTREGUE: as fases concluídas, agrupadas por versão, da mais recente para a mais antiga. Fase concluída sem versão aparece em "${"Sem versão (ainda não lançado)"}" — feita, mas ainda fora de uma versão fechada.
|
|
321
|
+
|
|
322
|
+
Não existe changelog separado do roadmap: isto é a leitura das mesmas fases com \`status: "concluida"\`. Por isso nunca diverge do que foi planejado.
|
|
323
|
+
|
|
324
|
+
Use para responder "o que entregamos na 1.0?" ou "o que já está pronto?". Para o corpo de uma fase específica, o endereço entre colchetes é o \`slug\`.`,
|
|
325
|
+
inputSchema: z.object({
|
|
326
|
+
versao: z
|
|
327
|
+
.string()
|
|
328
|
+
.optional()
|
|
329
|
+
.describe("Filtra por uma versão exata (rótulo livre, como \"1.0.0\" ou \"onda 2\")."),
|
|
330
|
+
}),
|
|
331
|
+
}, async ({ versao }) => {
|
|
332
|
+
try {
|
|
333
|
+
const fases = await leFases(raiz, { status: "concluida", ...(versao ? { versao } : {}) });
|
|
334
|
+
return texto(formataChangelog(fases));
|
|
335
|
+
}
|
|
336
|
+
catch (erro) {
|
|
337
|
+
return falha(erro);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
server.registerTool("criar_fase", {
|
|
341
|
+
description: `Cria uma fase no roadmap. Proponha ao humano antes: o roadmap é a direção do projeto, e uma fase nova é um compromisso.
|
|
342
|
+
|
|
343
|
+
Uma fase é uma etapa com checklist — o checklist vai DENTRO do conteúdo, em markdown (\`- [ ] item\`). A unidade de estado é a fase: marcar um item é editar o texto; concluir a fase é mudar o status.
|
|
344
|
+
|
|
345
|
+
\`status: "concluida"\` na criação é o caminho do trabalho NÃO planejado (um hotfix, um ajuste feito na hora) entrar no changelog: cria a fase já feita. Não invente uma fase "aberta" para algo que já aconteceu.
|
|
346
|
+
|
|
347
|
+
\`versao\` é rótulo livre (\`1.0.0\`, \`onda 2\`, \`lançamento\`) e opcional — pode ser atribuída depois, ao fechar uma versão. O slug (endereço) é derivado do título e fixo: escolha o título pensando nisso.`,
|
|
348
|
+
inputSchema: z.object({
|
|
349
|
+
titulo: z.string().min(2).max(120).describe("Curto e específico: `Autenticação por e-mail`, não `Fase 2`."),
|
|
350
|
+
conteudo: z
|
|
351
|
+
.string()
|
|
352
|
+
.max(50000)
|
|
353
|
+
.optional()
|
|
354
|
+
.describe("Markdown: o que a fase cobre e o checklist (`- [ ] item`). Pode ficar vazio e ser preenchido depois."),
|
|
355
|
+
versao: z
|
|
356
|
+
.string()
|
|
357
|
+
.max(40)
|
|
358
|
+
.optional()
|
|
359
|
+
.describe("Versão prevista ou, se já concluída, a versão em que saiu. Rótulo livre."),
|
|
360
|
+
status: z
|
|
361
|
+
.enum(["ideia", "aberta", "concluida"])
|
|
362
|
+
.optional()
|
|
363
|
+
.describe("Padrão `aberta`. `ideia` = não comprometida, fora do roadmap ativo. `concluida` = já feita (hotfix)."),
|
|
364
|
+
}),
|
|
365
|
+
}, async ({ titulo, conteudo, versao, status }) => {
|
|
366
|
+
try {
|
|
367
|
+
const { fase } = await criaFase(raiz, { titulo, conteudo, versao, status });
|
|
368
|
+
return texto(`Fase \`${fase.slug}\` criada — ordem ${fase.ordem}, ${fase.status}` +
|
|
369
|
+
(fase.versao ? `, versão ${fase.versao}` : "") +
|
|
370
|
+
(fase.status === "concluida" ? ". Já está no changelog." : "."));
|
|
371
|
+
}
|
|
372
|
+
catch (erro) {
|
|
373
|
+
return falha(erro);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
server.registerTool("editar_fase", {
|
|
377
|
+
description: `Edita uma fase pelo \`slug\`: título, conteúdo (o checklist), versão, status ou ordem. Mande só o que muda.
|
|
378
|
+
|
|
379
|
+
CONCLUIR uma fase é isto com \`status: "concluida"\`. Proponha ao humano antes — é ele quem decide que a fase acabou. Ao concluir, a fase sai do roadmap e entra no changelog sozinha; nada precisa ser movido ou reescrito. Reabrir é \`status: "aberta"\`. \`descartada\` tira do roadmap sem entrar no changelog e sem apagar o rastro.
|
|
380
|
+
|
|
381
|
+
Fase concluída NÃO gera memória no Brain por padrão. O roadmap guarda o quê e quando; o porquê de uma decisão só vira memória se passar nos três filtros — e aí o caminho é \`gravar_memoria\`, não o conteúdo da fase.
|
|
382
|
+
|
|
383
|
+
\`versao: null\` tira a versão. \`ordem\` reposiciona: a aberta de menor ordem é o "Agora".`,
|
|
384
|
+
inputSchema: z.object({
|
|
385
|
+
slug: z.string().min(1).describe("O endereço da fase, como aparece entre colchetes em `ler_roadmap` e `ler_changelog`."),
|
|
386
|
+
titulo: z.string().min(2).max(120).optional(),
|
|
387
|
+
conteudo: z.string().max(50000).optional().describe("Substitui o conteúdo inteiro — para marcar um item, mande o checklist completo com o item marcado."),
|
|
388
|
+
versao: z.string().max(40).nullable().optional().describe("Rótulo livre; `null` remove."),
|
|
389
|
+
status: z.enum(["ideia", "aberta", "concluida", "descartada"]).optional(),
|
|
390
|
+
ordem: z.number().int().positive().optional(),
|
|
391
|
+
}),
|
|
392
|
+
}, async ({ slug, ...mudancas }) => {
|
|
393
|
+
try {
|
|
394
|
+
const { fase } = await editaFase(raiz, slug, mudancas);
|
|
395
|
+
// A frase de transicao so quando a conclusao foi o PEDIDO — editar o texto de uma
|
|
396
|
+
// fase ja concluida nao a faz "sair do roadmap" de novo.
|
|
397
|
+
return texto(`Fase \`${fase.slug}\` atualizada — ${fase.status}` +
|
|
398
|
+
(fase.versao ? `, versão ${fase.versao}` : "") +
|
|
399
|
+
(mudancas.status === "concluida" ? ". Saiu do roadmap e entrou no changelog." : "."));
|
|
400
|
+
}
|
|
401
|
+
catch (erro) {
|
|
402
|
+
return falha(erro);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
295
405
|
return server;
|
|
296
406
|
}
|
|
297
|
-
|
|
407
|
+
/**
|
|
408
|
+
* `--help` / `--version` respondem e SAEM. Sem isto, qualquer invocacao com argumento
|
|
409
|
+
* caia no `serveStdio` e ficava esperando stdin para sempre — e foi assim que o
|
|
410
|
+
* aquecedor de cache do `dd-harness start` (que chamava `npx -y dd-harness-mcp --help`
|
|
411
|
+
* so para forcar o download) travou o wizard inteiro depois que o shebang passou a
|
|
412
|
+
* deixar o servidor subir de verdade.
|
|
413
|
+
*/
|
|
414
|
+
const args = process.argv.slice(2);
|
|
415
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
416
|
+
console.log(`dd-harness-mcp — servidor MCP do dd-harness (fala por stdio, nao por terminal)
|
|
417
|
+
|
|
418
|
+
Nao se roda a mao: quem o inicia e o host MCP (Claude Code, Codex, Cursor), pela
|
|
419
|
+
declaracao em \`.mcp.json\`. Sem um host do outro lado ele fica esperando mensagem
|
|
420
|
+
no stdin, que e o comportamento correto de um servidor stdio.
|
|
421
|
+
|
|
422
|
+
--help esta mensagem
|
|
423
|
+
--version a versao instalada
|
|
424
|
+
|
|
425
|
+
Para declarar no seu repositorio, rode \`dd-harness start\`.`);
|
|
426
|
+
}
|
|
427
|
+
else if (args.includes("--version") || args.includes("-V")) {
|
|
428
|
+
const { readFile } = await import("node:fs/promises");
|
|
429
|
+
const { fileURLToPath } = await import("node:url");
|
|
430
|
+
const { dirname, join } = await import("node:path");
|
|
431
|
+
// `dist/mcp/src/index.js` -> tres niveis ate a raiz do pacote.
|
|
432
|
+
const aqui = dirname(fileURLToPath(import.meta.url));
|
|
433
|
+
const lido = await readFile(join(aqui, "..", "..", "..", "package.json"), "utf8");
|
|
434
|
+
console.log(JSON.parse(lido).version ?? "desconhecida");
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
serveStdio(criaServidor);
|
|
438
|
+
}
|
package/package.json
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dd-harness-mcp",
|
|
3
|
-
"version": "0.
|
|
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 `npx -y dd-harness-mcp` numa cache limpa antes de publicar — NAO com `node dist/mcp/src/index.js`: rodar pelo caminho direto passa mesmo quando o pacote instalado esta quebrado, porque o defeito mora no shim que o npm gera. Sem o shebang em `src/index.ts` o shim `.cmd` do Windows chama o `.js` sem `node` na frente, e o servidor morre limpo (exit 0, em silencio) antes do handshake — o cliente so ve CONNECTION_CLOSED.",
|
|
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
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "dd-harness-mcp",
|
|
3
|
+
"version": "0.10.0",
|
|
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 `npx -y dd-harness-mcp` numa cache limpa antes de publicar — NAO com `node dist/mcp/src/index.js`: rodar pelo caminho direto passa mesmo quando o pacote instalado esta quebrado, porque o defeito mora no shim que o npm gera. Sem o shebang em `src/index.ts` o shim `.cmd` do Windows chama o `.js` sem `node` na frente, e o servidor morre limpo (exit 0, em silencio) antes do handshake — o cliente so ve CONNECTION_CLOSED.",
|
|
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
|
+
}
|