dd-harness-mcp 0.11.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.
@@ -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.`;
@@ -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> [...]');
@@ -714,9 +715,27 @@ function contextoDaSessao(r, worker) {
714
715
  "(`tipo: \"politica\"` e `tipo: \"briefing\"`, uma chamada cada). Se essa " +
715
716
  "ferramenta não estiver disponível nesta sessão, pare e diga ao usuário que " +
716
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" +
717
725
  "5. Depois de escrever os dois, sobrescreva o `CLAUDE.md` da raiz com um " +
718
726
  "ponteiro curto para a política deste serviço — a verdade passa a morar " +
719
- "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." +
720
739
  fila,
721
740
  };
722
741
  }
@@ -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
@@ -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
  *
@@ -384,7 +384,7 @@ CONCLUIR uma fase é isto com \`status: "concluida"\`. Proponha ao humano antes
384
384
 
385
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.
386
386
 
387
- \`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.`,
388
388
  inputSchema: z.object({
389
389
  slug: z.string().min(1).describe("O endereço da fase, como aparece entre colchetes em `ler_roadmap` e `ler_changelog`."),
390
390
  titulo: z.string().min(2).max(120).optional(),
@@ -395,12 +395,13 @@ Fase concluída NÃO gera memória no Brain por padrão. O roadmap guarda o quê
395
395
  }),
396
396
  }, async ({ slug, ...mudancas }) => {
397
397
  try {
398
- const { fase } = await editaFase(raiz, slug, mudancas);
398
+ const { fase, atras_de } = await editaFase(raiz, slug, mudancas);
399
399
  // A frase de transicao so quando a conclusao foi o PEDIDO — editar o texto de uma
400
400
  // fase ja concluida nao a faz "sair do roadmap" de novo.
401
401
  return texto(`Fase \`${fase.slug}\` atualizada — ${fase.status}` +
402
402
  (fase.versao ? `, versão ${fase.versao}` : "") +
403
- (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));
404
405
  }
405
406
  catch (erro) {
406
407
  return falha(erro);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-harness-mcp",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "description": "Servidor MCP do dd-harness: o agente consulta e grava memoria como ferramenta, sem passar por arquivo.",
6
6
  "license": "UNLICENSED",