dd-harness-mcp 0.10.0 → 0.12.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/check.js +2 -0
- package/dist/cli/src/diff.js +26 -2
- package/dist/cli/src/escreve-config.js +5 -1
- package/dist/cli/src/index.js +44 -4
- package/dist/cli/src/roadmap.js +21 -0
- package/dist/mcp/src/index.js +10 -5
- package/package.json +1 -1
package/dist/cli/src/check.js
CHANGED
|
@@ -99,6 +99,7 @@ export async function check(raiz, commit) {
|
|
|
99
99
|
fechadas: 0,
|
|
100
100
|
tocadas,
|
|
101
101
|
reancoragens,
|
|
102
|
+
semGit: mudancas.semGit,
|
|
102
103
|
};
|
|
103
104
|
}
|
|
104
105
|
const envio = await pede(`${config.api}/api/v1/deriva`, {
|
|
@@ -127,5 +128,6 @@ export async function check(raiz, commit) {
|
|
|
127
128
|
fechadas: julgamento.fechadas ?? 0,
|
|
128
129
|
tocadas,
|
|
129
130
|
reancoragens,
|
|
131
|
+
semGit: mudancas.semGit,
|
|
130
132
|
};
|
|
131
133
|
}
|
package/dist/cli/src/diff.js
CHANGED
|
@@ -17,10 +17,34 @@ export async function caminhosDoCommit(raiz, commit) {
|
|
|
17
17
|
const { stdout } = await roda("git", ["diff-tree", "--no-commit-id", "-r", "-M", "--name-status", commit], { cwd: raiz });
|
|
18
18
|
return interpretaNameStatus(stdout);
|
|
19
19
|
}
|
|
20
|
-
catch {
|
|
21
|
-
|
|
20
|
+
catch (erro) {
|
|
21
|
+
// Continua sem lancar — o gancho `post-commit` nao pode falhar por causa de um aviso.
|
|
22
|
+
// O que muda e que a razao volta junto, para quem chamou poder dize-la em vez de
|
|
23
|
+
// mostrar "nenhuma mudanca" quando na verdade nada foi consultado.
|
|
24
|
+
return { caminhos: [], renames: [], semGit: classificaFalhaDoGit(erro) };
|
|
22
25
|
}
|
|
23
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Traduz a falha do `git diff-tree` nos tres casos que pedem respostas diferentes de quem
|
|
29
|
+
* chamou: consertar o repositorio, conferir o SHA, ou instalar o git.
|
|
30
|
+
*
|
|
31
|
+
* A leitura e do stderr porque o `git` usa o mesmo codigo de saida (128) para "not a git
|
|
32
|
+
* repository" e para "bad object" — o texto e o unico jeito de separar os dois.
|
|
33
|
+
*/
|
|
34
|
+
function classificaFalhaDoGit(erro) {
|
|
35
|
+
const e = erro;
|
|
36
|
+
if (e.code === "ENOENT")
|
|
37
|
+
return "git-indisponivel";
|
|
38
|
+
const stderr = e.stderr ?? "";
|
|
39
|
+
if (/not a git repository/i.test(stderr))
|
|
40
|
+
return "sem-repositorio";
|
|
41
|
+
if (/bad object|unknown revision|ambiguous argument/i.test(stderr)) {
|
|
42
|
+
return "commit-desconhecido";
|
|
43
|
+
}
|
|
44
|
+
// Falha que nao sabemos nomear: ainda e melhor avisar que o cruzamento nao aconteceu do
|
|
45
|
+
// que deixar passar como silencio.
|
|
46
|
+
return "git-indisponivel";
|
|
47
|
+
}
|
|
24
48
|
/**
|
|
25
49
|
* Le a saida de `--name-status`: uma letra de status, TAB, e um ou dois caminhos.
|
|
26
50
|
*
|
|
@@ -107,4 +107,8 @@ de ler código, responder ou planejar.
|
|
|
107
107
|
|
|
108
108
|
- Se a ferramenta não existir, o servidor MCP do dd-harness não está declarado:
|
|
109
109
|
avise o usuário e **não modifique nada** até ele resolver.
|
|
110
|
-
- Se ela devolver vazio, este projeto nunca foi briefado — diga isso ao usuário
|
|
110
|
+
- Se ela devolver vazio, este projeto nunca foi briefado — diga isso ao usuário.
|
|
111
|
+
|
|
112
|
+
Depois, \`ler_roadmap\`: se houver uma fase **Agora**, é dela que saem os passos
|
|
113
|
+
desta sessão. Lista vazia significa que este projeto não usa roadmap, e isso é
|
|
114
|
+
válido — não crie fase sem o usuário pedir.`;
|
package/dist/cli/src/index.js
CHANGED
|
@@ -19,7 +19,7 @@ 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
|
+
import { avisoDeOrdem, blocoDeSessao, criaFase, editaFase, formataChangelog, formataRoadmap, leFases, } from "./roadmap.js";
|
|
23
23
|
import { busca } from "./buscar.js";
|
|
24
24
|
import { arquiva, edita, le } from "./curar.js";
|
|
25
25
|
import { criaPasta } from "./pasta.js";
|
|
@@ -155,7 +155,7 @@ async function comandoFase(argv) {
|
|
|
155
155
|
throw new Error("fase editar exige o slug da fase.");
|
|
156
156
|
const ordemBruta = argumento(opcoes, "ordem");
|
|
157
157
|
const status = statusDoArgv(opcoes);
|
|
158
|
-
const { fase } = await editaFase(process.cwd(), slug, {
|
|
158
|
+
const { fase, atras_de } = await editaFase(process.cwd(), slug, {
|
|
159
159
|
titulo: argumento(opcoes, "titulo"),
|
|
160
160
|
conteudo: await conteudoDoArgv(opcoes),
|
|
161
161
|
versao: opcoes.includes("--sem-versao") ? null : argumento(opcoes, "versao"),
|
|
@@ -165,7 +165,8 @@ async function comandoFase(argv) {
|
|
|
165
165
|
console.log(`Fase ${fase.slug} atualizada — ${fase.status}` +
|
|
166
166
|
(fase.versao ? `, versão ${fase.versao}` : "") +
|
|
167
167
|
(status === "concluida" ? " (saiu do roadmap, entrou no changelog)" : "") +
|
|
168
|
-
"."
|
|
168
|
+
"." +
|
|
169
|
+
avisoDeOrdem(fase, atras_de, ordemBruta !== undefined));
|
|
169
170
|
return;
|
|
170
171
|
}
|
|
171
172
|
throw new Error('uso: dd-harness fase criar --titulo "<t>" [...] | dd-harness fase editar <slug> [...]');
|
|
@@ -553,8 +554,29 @@ async function comandoReancorar(argv) {
|
|
|
553
554
|
console.log(` para ${r.para}`);
|
|
554
555
|
console.log(" Rode `dd-harness check --commit <sha>` para medir a base nova.");
|
|
555
556
|
}
|
|
557
|
+
/**
|
|
558
|
+
* Pediram `--commit` e o cruzamento com o diff nao aconteceu. Avisa ANTES do resto: sem
|
|
559
|
+
* isto a saida e identica a de um commit que nao tocou em nada indexado, e quem pediu o
|
|
560
|
+
* cruzamento nao tem como saber que ele nao existiu.
|
|
561
|
+
*
|
|
562
|
+
* Aviso, nao erro: o gancho `post-commit` termina em sucesso mesmo assim, e a medicao das
|
|
563
|
+
* ancoras (que nao depende de git) continua valendo.
|
|
564
|
+
*/
|
|
565
|
+
function avisaSemGit(motivo) {
|
|
566
|
+
const texto = {
|
|
567
|
+
"sem-repositorio": "esta pasta não é um repositório git — não há diff para cruzar com as âncoras.",
|
|
568
|
+
"commit-desconhecido": "o git não reconhece este commit — confira o SHA.",
|
|
569
|
+
"git-indisponivel": "não consegui rodar o git aqui.",
|
|
570
|
+
}[motivo];
|
|
571
|
+
console.log(`AVISO: ${texto}`);
|
|
572
|
+
console.log(" As âncoras foram medidas assim mesmo; só o cruzamento com o");
|
|
573
|
+
console.log(" commit ficou de fora.");
|
|
574
|
+
console.log("");
|
|
575
|
+
}
|
|
556
576
|
async function comandoCheck(argv) {
|
|
557
577
|
const r = await check(process.cwd(), argumento(argv, "commit"));
|
|
578
|
+
if (r.semGit)
|
|
579
|
+
avisaSemGit(r.semGit);
|
|
558
580
|
if (r.medidas === 0) {
|
|
559
581
|
console.log("nenhuma âncora para medir.");
|
|
560
582
|
return;
|
|
@@ -693,9 +715,27 @@ function contextoDaSessao(r, worker) {
|
|
|
693
715
|
"(`tipo: \"politica\"` e `tipo: \"briefing\"`, uma chamada cada). Se essa " +
|
|
694
716
|
"ferramenta não estiver disponível nesta sessão, pare e diga ao usuário que " +
|
|
695
717
|
"o servidor MCP do dd-harness precisa estar conectado para isto.\n" +
|
|
718
|
+
// A politica e escrita do zero pelo agente, e este texto e o unico lugar que diz o
|
|
719
|
+
// que ela precisa conter. Sem esta linha, o roadmap chega ao contexto e nao obriga
|
|
720
|
+
// a nada: informacao passiva, que e como uma feature existe sem ser usada.
|
|
721
|
+
" Na política, inclua uma linha sobre o roadmap: a fase **Agora** é o foco da " +
|
|
722
|
+
"sessão, e ao terminá-la o agente **propõe** concluí-la (`editar_fase` com " +
|
|
723
|
+
"`status: \"concluida\"`) — nunca conclui por conta própria, porque quem decide " +
|
|
724
|
+
"que uma fase acabou é o usuário.\n" +
|
|
696
725
|
"5. Depois de escrever os dois, sobrescreva o `CLAUDE.md` da raiz com um " +
|
|
697
726
|
"ponteiro curto para a política deste serviço — a verdade passa a morar " +
|
|
698
|
-
"aqui, e o `CLAUDE.md` local nunca mais precisa ser editado à mão." +
|
|
727
|
+
"aqui, e o `CLAUDE.md` local nunca mais precisa ser editado à mão. O ponteiro " +
|
|
728
|
+
"precisa citar `ler_artefato` **e** `ler_roadmap`: quem chega por ele (Codex, " +
|
|
729
|
+
"Cursor, ou o Claude Code quando o hook não roda) não tem outro jeito de " +
|
|
730
|
+
"descobrir que há roadmap.\n" +
|
|
731
|
+
// O momento certo de perguntar: quem acabou de descrever o projeto tem o contexto
|
|
732
|
+
// fresco para dizer por onde ele vai. Depois disso, ninguem mais pergunta — e uma
|
|
733
|
+
// feature que so se descobre lendo a lista de ferramentas nao se descobre.
|
|
734
|
+
"6. Por fim, pergunte se o trabalho deste projeto tem **fases** — algo que " +
|
|
735
|
+
"atravessa sessões, com ordem entre as partes. Se tiver, proponha as primeiras e " +
|
|
736
|
+
"crie-as com `criar_fase`; a de menor ordem vira o foco de cada sessão, e " +
|
|
737
|
+
"concluí-la a move para o changelog sozinha. Se for um projeto pequeno, diga que " +
|
|
738
|
+
"roadmap é opcional e siga sem ele — não insista." +
|
|
699
739
|
fila,
|
|
700
740
|
};
|
|
701
741
|
}
|
package/dist/cli/src/roadmap.js
CHANGED
|
@@ -123,6 +123,27 @@ export function formataRoadmap(fases) {
|
|
|
123
123
|
: "Nenhuma fase concluída ainda.");
|
|
124
124
|
return linhas.join("\n");
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* A frase sobre POSICAO que fecha a resposta de `editar_fase` — vazia quando `ordem` nao
|
|
128
|
+
* foi pedida, porque quem edita texto nao perguntou nada sobre posicao.
|
|
129
|
+
*
|
|
130
|
+
* Existe por um achado medido (rodada 005): pedir `ordem: 1` para promover uma fase a
|
|
131
|
+
* "Agora" quando outra mais antiga ja tem `ordem: 1` grava o valor, responde sucesso e
|
|
132
|
+
* nao muda nada visivel — a leitura desempata por `created_at`. O comportamento esta
|
|
133
|
+
* certo; o silencio e que ensinava o agente a seguir achando que promoveu a fase.
|
|
134
|
+
*
|
|
135
|
+
* Confirma a ordem aplicada mesmo sem empate: e o unico jeito de quem pediu saber que o
|
|
136
|
+
* numero pegou, e custa uma linha.
|
|
137
|
+
*/
|
|
138
|
+
export function avisoDeOrdem(fase, atrasDe, pediuOrdem) {
|
|
139
|
+
if (!pediuOrdem)
|
|
140
|
+
return "";
|
|
141
|
+
if (!atrasDe)
|
|
142
|
+
return ` Ordem ${fase.ordem}${fase.status === "aberta" ? " — é o \"Agora\"" : ""}.`;
|
|
143
|
+
return (` Ordem ${fase.ordem}, mas ainda atrás de \`${atrasDe.slug}\` (${atrasDe.titulo}) —` +
|
|
144
|
+
" fases com a mesma ordem desempatam pela mais antiga. Para passar à frente, use uma" +
|
|
145
|
+
" ordem menor ou mude a ordem da outra.");
|
|
146
|
+
}
|
|
126
147
|
/**
|
|
127
148
|
* O changelog: fases concluidas agrupadas por versao, na ordem em que a API devolveu (mais
|
|
128
149
|
* recente primeiro). Grupo sem versao recebe `SEM_VERSAO`. A ordem dos grupos e a da
|
package/dist/mcp/src/index.js
CHANGED
|
@@ -12,7 +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
|
+
import { avisoDeOrdem, criaFase, editaFase, formataChangelog, formataRoadmap, leFases, } from "../../cli/src/roadmap.js";
|
|
16
16
|
/**
|
|
17
17
|
* O mesmo servico, outra porta.
|
|
18
18
|
*
|
|
@@ -284,9 +284,13 @@ Conteúdo vazio é válido e significa apagar o artefato.`,
|
|
|
284
284
|
}, async ({ tipo, conteudo }) => {
|
|
285
285
|
try {
|
|
286
286
|
const r = await escreveArtefato(raiz, tipo, conteudo);
|
|
287
|
+
// Sem promessa de atraso: a politica nova ja vale para quem LER daqui em diante,
|
|
288
|
+
// inclusive nesta sessao (`ler_artefato` logo em seguida devolve o conteudo novo).
|
|
289
|
+
// O que nao muda sozinho e o que ja foi injetado no contexto pelo hook, no inicio
|
|
290
|
+
// da sessao — e essa e a distincao que a mensagem precisa fazer.
|
|
287
291
|
return texto(`${r.criou ? "Criado" : "Atualizado"} o ${r.artefato} — ${r.tamanho} caractere(s).` +
|
|
288
292
|
(tipo === "politica"
|
|
289
|
-
? "
|
|
293
|
+
? " Já vale para quem ler a partir de agora; o que foi carregado no início desta sessão não muda sozinho."
|
|
290
294
|
: ""));
|
|
291
295
|
}
|
|
292
296
|
catch (erro) {
|
|
@@ -380,7 +384,7 @@ CONCLUIR uma fase é isto com \`status: "concluida"\`. Proponha ao humano antes
|
|
|
380
384
|
|
|
381
385
|
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
386
|
|
|
383
|
-
\`versao: null\` tira a versão. \`ordem\` reposiciona: a aberta de menor ordem é o "Agora".`,
|
|
387
|
+
\`versao: null\` tira a versão. \`ordem\` reposiciona: a aberta de menor ordem é o "Agora". Duas fases podem ter a mesma ordem — aí a mais antiga vem primeiro, então repetir a ordem da fase atual NÃO promove a sua. A resposta diz em que posição a fase ficou e, se ela seguiu atrás de outra, qual.`,
|
|
384
388
|
inputSchema: z.object({
|
|
385
389
|
slug: z.string().min(1).describe("O endereço da fase, como aparece entre colchetes em `ler_roadmap` e `ler_changelog`."),
|
|
386
390
|
titulo: z.string().min(2).max(120).optional(),
|
|
@@ -391,12 +395,13 @@ Fase concluída NÃO gera memória no Brain por padrão. O roadmap guarda o quê
|
|
|
391
395
|
}),
|
|
392
396
|
}, async ({ slug, ...mudancas }) => {
|
|
393
397
|
try {
|
|
394
|
-
const { fase } = await editaFase(raiz, slug, mudancas);
|
|
398
|
+
const { fase, atras_de } = await editaFase(raiz, slug, mudancas);
|
|
395
399
|
// A frase de transicao so quando a conclusao foi o PEDIDO — editar o texto de uma
|
|
396
400
|
// fase ja concluida nao a faz "sair do roadmap" de novo.
|
|
397
401
|
return texto(`Fase \`${fase.slug}\` atualizada — ${fase.status}` +
|
|
398
402
|
(fase.versao ? `, versão ${fase.versao}` : "") +
|
|
399
|
-
(mudancas.status === "concluida" ? ". Saiu do roadmap e entrou no changelog." : ".")
|
|
403
|
+
(mudancas.status === "concluida" ? ". Saiu do roadmap e entrou no changelog." : ".") +
|
|
404
|
+
avisoDeOrdem(fase, atras_de, mudancas.ordem !== undefined));
|
|
400
405
|
}
|
|
401
406
|
catch (erro) {
|
|
402
407
|
return falha(erro);
|
package/package.json
CHANGED