dd-harness 0.3.0 → 0.5.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/index.js CHANGED
@@ -3,14 +3,14 @@ import { termosDaConsulta } from "./argv.js";
3
3
  import { check, status } from "./check.js";
4
4
  import { leConfigDoRepo, guardaToken } from "./config.js";
5
5
  import { grava } from "./gravar.js";
6
- import { init, SUGESTAO_MCP } from "./init.js";
7
- import { LINHA_DE_IMPORT } from "./materializa.js";
6
+ import { init, SUGESTAO_AGENTS, SUGESTAO_HOOK, SUGESTAO_MCP } from "./init.js";
8
7
  import { buscaPolitica } from "./politica.js";
9
8
  import { busca } from "./buscar.js";
10
- import { arquiva, edita } from "./curar.js";
9
+ import { arquiva, edita, le } from "./curar.js";
11
10
  import { criaPasta } from "./pasta.js";
12
11
  import { criaProjeto } from "./projeto.js";
13
- import { sync } from "./sync.js";
12
+ import { reancora } from "./reancorar.js";
13
+ import { subiuOWorker } from "./worker.js";
14
14
  /**
15
15
  * `dd-harness` — o cliente que materializa os artefatos no repositorio.
16
16
  *
@@ -19,80 +19,53 @@ import { sync } from "./sync.js";
19
19
  * em repositorio alheio tem que envelhecer bem, e cada dependencia e uma chance de nao
20
20
  * envelhecer.
21
21
  */
22
- const AJUDA = `dd-harness — materializa política, briefing e Brain no repositório
23
-
24
- dd-harness login --token <token> [--api <url>]
25
- guarda a credencial desta máquina
26
- dd-harness projeto <slug> --nome "<nome>" [--tenant <t>] [--api <url>]
27
- cria o projeto no serviço (antes do init)
28
- dd-harness init --tenant <t> --projeto <p> [--api <url>]
29
- prepara o repositório (config + CLAUDE.md)
30
- dd-harness pasta <slug> --definicao "o que entra e o que não entra"
31
- cria a pasta que o gravar exige
32
- dd-harness gravar <arquivo.md> registra uma memória a partir de um markdown
33
- dd-harness editar <arquivo.md> corrige o que já está gravado
34
- dd-harness arquivar <pasta>/<slug> --motivo <obsoleta|incorreta|fora_dos_filtros>
35
- [--substituida-por <pasta>/<slug>]
36
- tira de circulação sem apagar
37
- dd-harness buscar "<pergunta>" acha memória por relevância, não por arquivo
38
- dd-harness sync escreve os artefatos em disco
39
- dd-harness check [--commit <sha>] mede as âncoras e reporta a deriva
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
44
- dd-harness --help
45
-
46
- O gerado vive em dd-harness/. Na raiz fica só o seu CLAUDE.md, que importa a
47
- política com a linha ${LINHA_DE_IMPORT}
22
+ const AJUDA = `dd-harness — a política e o Brain do projeto, no serviço
23
+
24
+ dd-harness login --token <token> [--api <url>]
25
+ guarda a credencial desta máquina
26
+ dd-harness projeto <slug> --nome "<nome>" [--tenant <t>] [--api <url>]
27
+ cria o projeto no serviço (antes do init)
28
+ dd-harness init --tenant <t> --projeto <p> [--api <url>]
29
+ prepara o repositório (config + CLAUDE.md)
30
+ dd-harness pasta <slug> --definicao "o que entra e o que não entra"
31
+ cria a pasta que o gravar exige
32
+ dd-harness gravar <arquivo.md> registra uma memória a partir de um markdown
33
+ dd-harness editar <arquivo.md> corrige o que já está gravado
34
+ dd-harness arquivar <pasta>/<slug> --motivo <obsoleta|incorreta|fora_dos_filtros>
35
+ [--substituida-por <pasta>/<slug>]
36
+ tira de circulação sem apagar
37
+ dd-harness buscar "<pergunta>" acha memória por relevância, não por arquivo
38
+ dd-harness ler <pasta>/<slug> imprime a memória inteira, no formato de gravar
39
+ dd-harness reancorar <pasta>/<slug> --de "<alvo>" --para "<alvo>"
40
+ troca o alvo de uma âncora que mudou de lugar
41
+ dd-harness check [--commit <sha>] mede as âncoras e reporta a deriva
42
+ dd-harness status só lê: o tamanho do Brain e o que espera julgamento
43
+ dd-harness politica [--hook] imprime a política do serviço
44
+ saída 0 = veio; 3 = projeto sem política;
45
+ 1 = não consegui buscar
46
+ --hook: fala o protocolo do SessionStart do
47
+ Claude Code, para pôr a política no contexto
48
+ dd-harness --help
49
+
50
+ Nada do dd-harness fica em disco: a política chega pelo hook de sessão, e a
51
+ memória pela busca, na hora.
48
52
  `;
49
- /** O aviso existe porque o import falha em silêncio — foi medido, não suposto. */
50
- function avisaSobreOPonteiro(ponteiro) {
51
- if (ponteiro === "ok" || ponteiro === "sem-politica")
52
- return;
53
- // O import pendurado e o inverso dos outros dois: a linha esta la, o alvo e que nao
54
- // existe. Dizer "acrescente a linha" aqui mandaria a pessoa para o lugar errado.
55
- if (ponteiro === "aponta-para-o-vazio") {
56
- console.error([
57
- "",
58
- `AVISO: o CLAUDE.md importa ${LINHA_DE_IMPORT}, mas não há política no serviço.`,
59
- "O arquivo apontado não existe, e import quebrado falha em silêncio: a sessão abre",
60
- "sem protocolo e nada avisa.",
61
- "",
62
- "Escreva a política do projeto no serviço, ou tire a linha do CLAUDE.md.",
63
- ].join("\n"));
64
- return;
65
- }
66
- const motivo = ponteiro === "sem-claude-md"
67
- ? "não há CLAUDE.md na raiz"
68
- : "o CLAUDE.md da raiz não importa a política";
69
- console.error([
70
- "",
71
- `AVISO: ${motivo}.`,
72
- "A política existe no serviço e está em disco, mas não chega à sessão: o import",
73
- "ausente falha em silêncio, e a sessão abre sem protocolo sem avisar ninguém.",
74
- "",
75
- `Acrescente esta linha ao CLAUDE.md da raiz: ${LINHA_DE_IMPORT}`,
76
- "Ou rode: dd-harness init --tenant <t> --projeto <p>",
77
- ].join("\n"));
78
- }
79
53
  /**
80
54
  * Os ganchos sao IMPRESSOS, nunca instalados. `.git/hooks` nao e versionado e nao e
81
55
  * nosso: escrever la dentro sem a pessoa pedir e o mesmo tipo de invasao que
82
56
  * sobrescrever o CLAUDE.md dela. Quem cola, decide.
83
57
  */
84
- const GANCHOS = `
85
- Opcional — dois ganchos que valem a pena:
86
-
87
- .git/hooks/post-commit (avisa quais memórias falam do que você mudou)
88
- #!/bin/sh
89
- dd-harness check --commit "$(git rev-parse HEAD)" || true
90
-
91
- .claude/settings.json (na abertura da sessão, o que espera julgamento)
92
- "hooks": { "SessionStart": [{ "hooks": [{ "type": "command",
93
- "command": "dd-harness status" }] }] }
94
-
95
- Os dois terminam em sucesso mesmo com deriva: avisam, não bloqueiam.`;
58
+ const GANCHOS = `
59
+ Opcional — o gancho que devolve a memória ao code review:
60
+
61
+ .git/hooks/post-commit (avisa quais memórias falam do que você mudou)
62
+ #!/bin/sh
63
+ dd-harness check --commit "$(git rev-parse HEAD)" || true
64
+
65
+ Termina em sucesso mesmo com deriva: avisa, não bloqueia.
66
+
67
+ O hook da política (\`dd-harness politica --hook\`) é outra coisa, e não é
68
+ opcional — \`dd-harness init\` imprime a linha para o \`.claude/settings.json\`.`;
96
69
  function argumento(argv, nome) {
97
70
  const i = argv.indexOf(`--${nome}`);
98
71
  return i >= 0 ? argv[i + 1] : undefined;
@@ -109,20 +82,39 @@ async function comandoInit(argv) {
109
82
  api: argumento(argv, "api"),
110
83
  });
111
84
  console.log(r.config === "criada" ? "criado .dd-harness.json" : "mantido .dd-harness.json");
112
- console.log({
113
- criado: "criado CLAUDE.md com a linha de import",
114
- "linha-acrescentada": "ajustado CLAUDE.md linha de import acrescentada ao seu",
115
- "ja-tinha-a-linha": "mantido CLAUDE.md — já importava a política",
116
- }[r.claudeMd]);
117
- console.log("\nAgora: dd-harness login --token <token> && dd-harness sync");
85
+ console.log("\nAgora: dd-harness login --token <token>");
86
+ // O hook vem primeiro e nao e opcional: sem ele a sessao abre sem politica, que e a
87
+ // falha que este projeto existe para combater. O MCP e conveniencia; este, nao.
88
+ if (r.hook === "ja-declarado") {
89
+ console.log("\nmantido .claude/settings.json — o hook da política já está declarado");
90
+ }
91
+ else {
92
+ console.log("\nOBRIGATÓRIO: o hook que carrega a política no início de cada sessão." +
93
+ "\nSem ele a sessão abre sem protocolo, e nada avisa. Acrescente ao" +
94
+ "\n`.claude/settings.json` (não escrevo nele: o arquivo é seu e pode já" +
95
+ "\nter hooks e permissões):\n");
96
+ console.log(SUGESTAO_HOOK);
97
+ }
118
98
  if (r.mcp === "ja-declarado") {
119
99
  console.log("\nmantido .mcp.json — o servidor dd-harness já está declarado");
100
+ }
101
+ else {
102
+ console.log("\nOpcional: as memórias como ferramenta, para o agente buscar e gravar sem" +
103
+ "\nescrever arquivo. Acrescente ao `.mcp.json` da raiz (não escrevo nele: o" +
104
+ "\narquivo é seu e pode declarar outros servidores):\n");
105
+ console.log(SUGESTAO_MCP);
106
+ }
107
+ // Fora do Claude Code o hook nao roda, e ai o MCP deixa de ser conveniencia: e o unico
108
+ // caminho da politica. Mas ferramenta disponivel nao e ferramenta chamada — sem esta
109
+ // instrucao, o agente pode nunca perguntar.
110
+ if (r.agents === "ja-aponta") {
111
+ console.log("\nmantido AGENTS.md — já manda buscar a política pelo MCP");
120
112
  return;
121
113
  }
122
- console.log("\nOpcional: as memórias como ferramenta, para o agente buscar e gravar sem" +
123
- "\nescrever arquivo. Acrescente ao `.mcp.json` da raiz (não escrevo nele: o" +
124
- "\narquivo é seu e pode declarar outros servidores):\n");
125
- console.log(SUGESTAO_MCP);
114
+ console.log("\nUsa Codex, Cursor, Gemini CLI ou Windsurf? Eles NÃO rodam o hook do" +
115
+ "\nClaude Code, e sem isto a sessão abre sem política. Crie um `AGENTS.md`" +
116
+ "\nna raiz (ou acrescente ao seu) com:\n");
117
+ console.log(SUGESTAO_AGENTS);
126
118
  }
127
119
  /**
128
120
  * Nao exige `.dd-harness.json`, e isso importa: sem projeto no servico o `init` nao tem o
@@ -147,7 +139,7 @@ async function comandoEditar(argv) {
147
139
  }
148
140
  const r = await edita(process.cwd(), caminho);
149
141
  console.log(`editado ${r.endereco}`);
150
- console.log(` ${r.ancoras} âncora(s). Rode \`dd-harness sync\` para materializar.`);
142
+ console.log(` ${r.ancoras} âncora(s).`);
151
143
  }
152
144
  const MOTIVOS = ["obsoleta", "incorreta", "fora_dos_filtros"];
153
145
  async function comandoArquivar(argv) {
@@ -166,7 +158,20 @@ async function comandoArquivar(argv) {
166
158
  substituidaPor: argumento(argv, "substituida-por"),
167
159
  });
168
160
  console.log(`arquivado ${r.endereco} (${r.motivo})`);
169
- console.log(" foi para o histórico, não foi apagada. Rode `dd-harness sync`.");
161
+ console.log(" foi para o histórico, não foi apagada.");
162
+ }
163
+ /**
164
+ * A memoria inteira no stdout, no mesmo markdown que `gravar` e `editar` consomem.
165
+ *
166
+ * Sem materializacao este e o unico caminho para o corpo: a busca devolve so endereco,
167
+ * titulo e resumo. Tambem e o ponto de partida de toda edicao — corrigir exige ver.
168
+ */
169
+ async function comandoLer(argv) {
170
+ const endereco = argv[0];
171
+ if (!endereco || endereco.startsWith("-")) {
172
+ throw new Error("uso: dd-harness ler <pasta>/<slug>");
173
+ }
174
+ console.log(await le(process.cwd(), endereco));
170
175
  }
171
176
  async function comandoBuscar(argv) {
172
177
  const consulta = termosDaConsulta(argv);
@@ -181,6 +186,7 @@ async function comandoBuscar(argv) {
181
186
  if (!r.semantica) {
182
187
  console.log(" (só busca textual: sem provedor de embedding no serviço)");
183
188
  }
189
+ avisaSobreAFila(r.esperandoIndexacao);
184
190
  return;
185
191
  }
186
192
  console.log(r.semantica
@@ -190,36 +196,32 @@ async function comandoBuscar(argv) {
190
196
  console.log(` ${a.endereco} — ${a.titulo}`);
191
197
  console.log(` ${a.resumo}`);
192
198
  }
199
+ avisaSobreAFila(r.esperandoIndexacao);
193
200
  }
194
- async function comandoSync() {
195
- const resultado = await sync(process.cwd());
196
- if (resultado.tipo === "editado-a-mao") {
197
- console.error([
198
- "parei sem escrever nada: estes arquivos foram editados à mão.",
199
- ...resultado.arquivos.map((a) => ` ${a}`),
200
- "",
201
- "O disco é projeção do serviço, uma direção só. Duas saídas:",
202
- "",
203
- " 1. Leve a edição para o serviço — `dd-harness editar <arquivo>` para cada um",
204
- " acima. É o caminho normal de corrigir memória, e destrava o sync.",
205
- " 2. Descarte a edição local (git checkout / apague o arquivo) e sincronize.",
206
- ].join("\n"));
207
- process.exitCode = 1;
201
+ /**
202
+ * Memoria sem vetor nao aparece na busca semantica, e a resposta parece completa do
203
+ * mesmo jeito. `semantica: true` diz so que a CONSULTA foi vetorizada — a base pode
204
+ * estar inteira na fila, e ai a busca responde lexical com cara de semantica.
205
+ */
206
+ function avisaSobreAFila(esperando) {
207
+ if (esperando <= 0)
208
208
  return;
209
+ console.log("");
210
+ console.log(`ATENÇÃO: ${esperando} memória(s) ainda sem vetor — podem existir respostas`);
211
+ console.log(" melhores que não apareceram aqui. Rode `start-worker.bat`.");
212
+ }
213
+ async function comandoReancorar(argv) {
214
+ const endereco = argv[0];
215
+ const de = argumento(argv, "de");
216
+ const para = argumento(argv, "para");
217
+ if (!endereco || endereco.startsWith("-") || !endereco.includes("/") || !de || !para) {
218
+ throw new Error('uso: dd-harness reancorar <pasta>/<slug> --de "<alvo>" --para "<alvo>"');
209
219
  }
210
- if (resultado.tipo === "sem-mudanca") {
211
- console.log("nada mudou no serviço — disco já está em dia.");
212
- }
213
- else {
214
- for (const a of resultado.escritos)
215
- console.log(`escrito ${a}`);
216
- for (const a of resultado.removidos)
217
- console.log(`removido ${a}`);
218
- if (!resultado.escritos.length && !resultado.removidos.length) {
219
- console.log("conteúdo novo do serviço, sem diferença em disco.");
220
- }
221
- }
222
- avisaSobreOPonteiro(resultado.ponteiro);
220
+ const r = await reancora(process.cwd(), endereco, de, para);
221
+ console.log(`reancorado ${r.endereco}`);
222
+ console.log(` de ${r.de}`);
223
+ console.log(` para ${r.para}`);
224
+ console.log(" Rode `dd-harness check --commit <sha>` para medir a base nova.");
223
225
  }
224
226
  async function comandoCheck(argv) {
225
227
  const r = await check(process.cwd(), argumento(argv, "commit"));
@@ -236,8 +238,24 @@ async function comandoCheck(argv) {
236
238
  if (r.ausentes.length) {
237
239
  console.log("");
238
240
  console.log("ALVO AUSENTE — alguém apagou ou moveu o que uma memória guarda:");
239
- for (const valor of r.ausentes)
241
+ for (const valor of r.ausentes) {
240
242
  console.log(` ${valor}`);
243
+ // Quando o git achou para onde o conteúdo foi, a saída deixa de ser só um
244
+ // diagnóstico e passa a ter uma ação — que é o que faltava numa refatoração
245
+ // grande, onde a lista de ausentes vira uma parede sem resposta.
246
+ const sugerida = r.reancoragens.find((s) => s.ancora === valor);
247
+ if (sugerida) {
248
+ console.log(` → provavelmente virou ${sugerida.sugestao}` +
249
+ (sugerida.similaridade ? ` (git: ${sugerida.similaridade}% similar)` : ""));
250
+ console.log(` dd-harness reancorar ${sugerida.pasta}/${sugerida.memoria}` +
251
+ ` --de "${valor}" --para "${sugerida.sugestao}"`);
252
+ }
253
+ }
254
+ if (r.reancoragens.length) {
255
+ console.log("");
256
+ console.log(" A sugestão vem da detecção de rename do git, por similaridade de");
257
+ console.log(" conteúdo — confira antes de aplicar. Nada é reancorado sozinho.");
258
+ }
241
259
  }
242
260
  if (r.novas > 0) {
243
261
  console.log("");
@@ -273,8 +291,20 @@ async function comandoCheck(argv) {
273
291
  * um "falhou" generico colapsaria: projeto novo (que precisa de briefing) de politica
274
292
  * inalcancavel (que precisa PARAR a sessao). Mudar estes numeros quebra o hook.
275
293
  */
276
- async function comandoPolitica() {
294
+ async function comandoPolitica(argv) {
277
295
  const r = await buscaPolitica(process.cwd());
296
+ // `--hook`: fala o protocolo do SessionStart do Claude Code, que injeta
297
+ // `additionalContext` no contexto da sessao. Sem a flag, saida legivel para quem roda
298
+ // no terminal. A diferenca importa: o hook precisa que o AVISO chegue ao modelo, e
299
+ // stderr so chega ao transcript — aviso que o modelo nao le e o mesmo que silencio.
300
+ if (argv.includes("--hook")) {
301
+ // A abertura da sessao e o momento certo de esvaziar a fila: o worker nao esta
302
+ // hospedado, e memoria sem vetor some da busca sem nada denunciar. Sobe so quando ha
303
+ // fila de verdade — o hook roda ate nas sessoes que so leem codigo.
304
+ const worker = await subiuOWorker(process.cwd(), r.esperandoIndexacao ?? 0);
305
+ console.log(JSON.stringify({ hookSpecificOutput: contextoDaSessao(r, worker) }));
306
+ return;
307
+ }
278
308
  if (r.estado === "ok") {
279
309
  console.log(r.conteudo);
280
310
  return;
@@ -287,6 +317,72 @@ async function comandoPolitica() {
287
317
  console.error(`não consegui buscar a política: ${r.motivo}`);
288
318
  process.exitCode = 1;
289
319
  }
320
+ /**
321
+ * O que o hook injeta no contexto, por estado.
322
+ *
323
+ * Sai sempre com codigo 0: o que precisa chegar ao modelo e o TEXTO, e um codigo de erro
324
+ * so faria o Claude Code registrar falha no transcript — que ninguem le — enquanto a
325
+ * sessao seguiria sem saber que esta sem protocolo.
326
+ */
327
+ function contextoDaSessao(r, worker) {
328
+ const base = { hookEventName: "SessionStart" };
329
+ const fila = avisoDaFila(r.esperandoIndexacao ?? 0, worker);
330
+ if (r.estado === "ok") {
331
+ return {
332
+ ...base,
333
+ additionalContext: "# Política deste projeto (carregada do dd-harness)\n\n" +
334
+ "As regras abaixo valem para esta sessão inteira.\n\n" +
335
+ r.conteudo +
336
+ fila,
337
+ };
338
+ }
339
+ if (r.estado === "sem-politica") {
340
+ return {
341
+ ...base,
342
+ additionalContext: "AVISO DO DD-HARNESS: este projeto existe no serviço mas **nunca foi briefado** " +
343
+ "— não há política.\n\nIsto não é uma falha: é um projeto novo. Antes de " +
344
+ "implementar qualquer coisa, diga isso ao usuário e proponha rodar `/briefar`." +
345
+ fila,
346
+ };
347
+ }
348
+ return {
349
+ ...base,
350
+ additionalContext: "PARE: NÃO FOI POSSÍVEL CARREGAR A POLÍTICA DESTE PROJETO.\n\n" +
351
+ `Motivo: ${r.motivo}\n\n` +
352
+ "A política pode existir no serviço e não ter chegado até aqui, então esta sessão " +
353
+ "está **sem protocolo** — as proibições e a regra do OK não foram carregadas.\n\n" +
354
+ "Antes de qualquer outra coisa: avise o usuário com estas palavras e **não " +
355
+ "modifique nenhum arquivo** até ele decidir como prosseguir. Seguir como se nada " +
356
+ "tivesse acontecido é exatamente a falha que este projeto combate.",
357
+ };
358
+ }
359
+ /**
360
+ * O estado da fila de indexacao, para ir junto da politica no contexto.
361
+ *
362
+ * Fila vazia nao gera linha nenhuma: aviso sem motivo em toda sessao e o que ensina a
363
+ * ignorar aviso. O texto muda conforme o worker subiu ou nao, porque a acao que se espera
364
+ * do agente e diferente em cada caso.
365
+ */
366
+ function avisoDaFila(esperando, worker) {
367
+ if (esperando <= 0)
368
+ return "";
369
+ if (worker.subiu) {
370
+ return (`\n\n---\n\nNOTA DO DD-HARNESS: ${esperando} memória(s) estavam sem vetor, e o ` +
371
+ "worker de indexação foi iniciado automaticamente agora. Até ele terminar, " +
372
+ "`buscar_memoria` pode não encontrar o que foi gravado recentemente — se uma " +
373
+ "busca vier vazia nos próximos minutos, tente de novo antes de concluir que a " +
374
+ "memória não existe.");
375
+ }
376
+ // Sem worker local (o caso do repositorio consumidor) ou falha ao subir: so avisar.
377
+ const comoResolver = worker.motivo === "sem-worker"
378
+ ? "O worker não roda a partir deste repositório — ele vive no monorepo do " +
379
+ "dd-harness. Avise o usuário que a indexação está pendente lá."
380
+ : `Não consegui iniciar o worker${worker.detalhe ? ` (${worker.detalhe})` : ""}. ` +
381
+ "Peça ao usuário para rodar `start-worker.bat`.";
382
+ return (`\n\n---\n\nAVISO DO DD-HARNESS: ${esperando} memória(s) estão sem vetor e **não ` +
383
+ "aparecem na busca semântica**. A busca vai responder mesmo assim, o que a faz " +
384
+ `parecer completa quando não está.\n\n${comoResolver}`);
385
+ }
290
386
  async function comandoStatus() {
291
387
  const r = await status(process.cwd());
292
388
  // O acervo vem primeiro, e sempre: e a resposta para "o que ha no Brain deste projeto?",
@@ -361,9 +457,8 @@ async function comandoPasta(argv) {
361
457
  }
362
458
  /**
363
459
  * O agente escreve o arquivo — que e o que ele ja fazia no modelo file-based — e este
364
- * comando o transforma em requisicao. O arquivo nao fica no repositorio: quem
365
- * materializa e o `sync`, a partir do servico, para nao existir copia escrita a mao ao
366
- * lado da copia gerada.
460
+ * comando o transforma em requisicao. O arquivo e so o veiculo: depois de gravado, a
461
+ * memoria vive no servico, e quem quiser le-la usa a busca. Nada fica em disco.
367
462
  */
368
463
  async function comandoGravar(argv) {
369
464
  const caminho = argv[0];
@@ -372,8 +467,7 @@ async function comandoGravar(argv) {
372
467
  }
373
468
  const r = await grava(process.cwd(), caminho);
374
469
  console.log(`gravado ${r.endereco}`);
375
- console.log(` ${r.ancoras} âncora(s), valendo para ${r.projetos} projeto(s).` +
376
- " Rode `dd-harness sync` para materializar.");
470
+ console.log(` ${r.ancoras} âncora(s), valendo para ${r.projetos} projeto(s).`);
377
471
  }
378
472
  async function principal() {
379
473
  const [comando, ...resto] = process.argv.slice(2);
@@ -392,16 +486,18 @@ async function principal() {
392
486
  return comandoEditar(resto);
393
487
  case "arquivar":
394
488
  return comandoArquivar(resto);
489
+ case "ler":
490
+ return comandoLer(resto);
395
491
  case "buscar":
396
492
  return comandoBuscar(resto);
397
- case "sync":
398
- return comandoSync();
493
+ case "reancorar":
494
+ return comandoReancorar(resto);
399
495
  case "check":
400
496
  return comandoCheck(resto);
401
497
  case "status":
402
498
  return comandoStatus();
403
499
  case "politica":
404
- return comandoPolitica();
500
+ return comandoPolitica(resto);
405
501
  case "--help":
406
502
  case "-h":
407
503
  case undefined:
package/dist/init.d.ts CHANGED
@@ -1,16 +1,52 @@
1
+ /**
2
+ * Prepara um repositorio para o dd-harness.
3
+ *
4
+ * Escreve UM arquivo: o `.dd-harness.json`, que diz a que projeto este repositorio
5
+ * pertence. Todo o resto e sugestao impressa, para quem cola decidir.
6
+ *
7
+ * Ate a fase 0 este comando tambem escrevia uma linha de import no `CLAUDE.md`, que
8
+ * apontava para a politica materializada em disco. Isso acabou: a politica chega pelo
9
+ * hook de sessao, e nada do dd-harness fica em disco.
10
+ */
1
11
  export type ResultadoDoInit = {
2
12
  config: "criada" | "ja-existia";
3
- claudeMd: "criado" | "linha-acrescentada" | "ja-tinha-a-linha";
4
13
  mcp: "ja-declarado" | "a-declarar";
14
+ hook: "ja-declarado" | "a-declarar";
15
+ /** O `AGENTS.md` manda o agente de fora do Claude Code buscar a política? */
16
+ agents: "ja-aponta" | "a-apontar";
5
17
  };
6
18
  /**
7
19
  * O `.mcp.json` e sugerido, nunca escrito.
8
20
  *
9
- * Pelo mesmo motivo do `CLAUDE.md` da raiz: o arquivo e do repositorio, pode ja declarar
10
- * outros servidores, e mesclar JSON alheio e onde falha silenciosa nasce entrada errada
11
- * nao da erro, a ferramenta so nao aparece. Quem cola sabe o que colou.
21
+ * O arquivo e do repositorio, pode ja declarar outros servidores, e mesclar JSON alheio e
22
+ * onde falha silenciosa nasce entrada errada nao da erro, a ferramenta so nao aparece.
23
+ * Quem cola sabe o que colou.
12
24
  */
13
25
  export declare const SUGESTAO_MCP = "{\n \"mcpServers\": {\n \"dd-harness\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"dd-harness-mcp\"]\n }\n }\n}";
26
+ /**
27
+ * O hook que carrega a politica no inicio de cada sessao.
28
+ *
29
+ * E a garantia de que nenhuma sessao abre sem protocolo — o papel que antes era do
30
+ * arquivo materializado mais a linha de import. Vive num hook, e nao numa instrucao no
31
+ * `CLAUDE.md`, porque instrucao o modelo pode pular: o import quebrado falhava em
32
+ * silencio, e isso foi medido.
33
+ *
34
+ * Sugerido e nao escrito, pelo mesmo motivo do `.mcp.json`.
35
+ */
36
+ export declare const SUGESTAO_HOOK = "{\n \"hooks\": {\n \"SessionStart\": [\n {\n \"hooks\": [\n {\n \"type\": \"command\",\n \"command\": \"dd-harness politica --hook\",\n \"statusMessage\": \"Carregando a pol\u00EDtica do dd-harness...\"\n }\n ]\n }\n ]\n }\n}";
37
+ /**
38
+ * O que escrever num `AGENTS.md`, para agente que NAO e o Claude Code.
39
+ *
40
+ * O hook de `SessionStart` e um mecanismo do Claude Code: Codex, Cursor, Gemini CLI e
41
+ * Windsurf nao o executam. Nessas ferramentas nada carrega a politica sozinho — e o
42
+ * resultado e a mesma falha de sempre, a sessao abrindo sem protocolo sem avisar.
43
+ *
44
+ * O que resta la e o servidor MCP, que essas ferramentas suportam. Mas ferramenta
45
+ * disponivel nao e ferramenta chamada: sem uma instrucao explicita, o agente pode
46
+ * simplesmente nunca perguntar pela politica. Dai esta linha, que e curta de proposito —
47
+ * ela manda buscar a regra, nao repete a regra.
48
+ */
49
+ export declare const SUGESTAO_AGENTS = "# AGENTS.md\n\nEste projeto tem um protocolo de trabalho que **n\u00E3o est\u00E1 neste arquivo**.\n\n**ANTES de qualquer outra coisa**, chame a ferramenta MCP `ler_artefato` com\n`tipo: \"politica\"` e siga o que ela devolver. \u00C9 a primeira a\u00E7\u00E3o da sess\u00E3o, antes\nde ler c\u00F3digo, responder ou planejar.\n\n- Se a ferramenta n\u00E3o existir, o servidor MCP do dd-harness n\u00E3o est\u00E1 declarado:\n avise o usu\u00E1rio e **n\u00E3o modifique nada** at\u00E9 ele resolver.\n- Se ela devolver vazio, este projeto nunca foi briefado \u2014 diga isso ao usu\u00E1rio.\n\nO Claude Code carrega a pol\u00EDtica sozinho, por hook. Nas outras ferramentas, a\nchamada acima \u00E9 o que substitui esse hook.";
14
50
  export declare function init(raiz: string, dados: {
15
51
  tenant: string;
16
52
  projeto: string;