dd-harness 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.
Files changed (2) hide show
  1. package/dist/index.js +126 -57
  2. package/package.json +44 -44
package/dist/index.js CHANGED
@@ -2,6 +2,15 @@
2
2
  import { spawn } from "node:child_process";
3
3
  import { access } from "node:fs/promises";
4
4
  import { join } from "node:path";
5
+ // DEP0190: Node imprime esse warning direto no stderr no instante em que
6
+ // `spawn(..., { shell: true })` recebe um array de args — de forma sincrona, ANTES de
7
+ // qualquer listener rodar, entao filtrar dentro de um `process.on("warning")` normal nao
8
+ // bloqueia a impressao (so o listener default e que imprime, e ele continua registrado
9
+ // junto do nosso). Por isso remover o listener default e a unica forma de suprimir.
10
+ // Real quando algum arg vem de fora, mas `instalaMcp` (mais abaixo) so passa literais
11
+ // fixos — sem isto o wizard de `start` mostrava um "DeprecationWarning" no meio do fluxo
12
+ // que parecia falha para quem esta vendo por dentro pela primeira vez.
13
+ process.removeAllListeners("warning");
5
14
  import { termosDaConsulta } from "./argv.js";
6
15
  import { check, status } from "./check.js";
7
16
  import { CAMINHO_CONFIG, guardaConfigDaMaquina, guardaToken, leConfigDaMaquina, leConfigDoRepo, leToken, } from "./config.js";
@@ -24,55 +33,55 @@ import { subiuOWorker } from "./worker.js";
24
33
  * em repositorio alheio tem que envelhecer bem, e cada dependencia e uma chance de nao
25
34
  * envelhecer.
26
35
  */
27
- const AJUDA = `dd-harness — a política e o Brain do projeto, no serviço
28
-
29
- dd-harness start numa pasta vazia: conduz tudo (login,
30
- espaço, projeto, config) numa tacada
31
- dd-harness login --token <token> [--api <url>]
32
- guarda a credencial desta máquina
33
- dd-harness projeto <slug> --nome "<nome>" [--tenant <t>] [--api <url>]
34
- cria o projeto no serviço (antes do init)
35
- dd-harness init --tenant <t> --projeto <p> [--api <url>]
36
- prepara o repositório (config + CLAUDE.md)
37
- dd-harness pasta <slug> --definicao "o que entra e o que não entra"
38
- cria a pasta que o gravar exige
39
- dd-harness gravar <arquivo.md> registra uma memória a partir de um markdown
40
- dd-harness editar <arquivo.md> corrige o que já está gravado
41
- dd-harness arquivar <pasta>/<slug> --motivo <obsoleta|incorreta|fora_dos_filtros>
42
- [--substituida-por <pasta>/<slug>]
43
- tira de circulação sem apagar
44
- dd-harness buscar "<pergunta>" acha memória por relevância, não por arquivo
45
- dd-harness ler <pasta>/<slug> imprime a memória inteira, no formato de gravar
46
- dd-harness reancorar <pasta>/<slug> --de "<alvo>" --para "<alvo>"
47
- troca o alvo de uma âncora que mudou de lugar
48
- dd-harness check [--commit <sha>] mede as âncoras e reporta a deriva
49
- dd-harness status só lê: o tamanho do Brain e o que espera julgamento
50
- dd-harness politica [--hook] imprime a política do serviço
51
- saída 0 = veio; 3 = projeto sem política;
52
- 1 = não consegui buscar
53
- --hook: fala o protocolo do SessionStart do
54
- Claude Code, para pôr a política no contexto
55
- dd-harness --help
56
- dd-harness --version qual binário está instalado nesta máquina
57
-
58
- Nada do dd-harness fica em disco: a política chega pelo hook de sessão, e a
59
- memória pela busca, na hora.
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 --help
65
+ dd-harness --version qual binário está instalado nesta máquina
66
+
67
+ Nada do dd-harness fica em disco: a política chega pelo hook de sessão, e a
68
+ memória pela busca, na hora.
60
69
  `;
61
70
  /**
62
71
  * Os ganchos sao IMPRESSOS, nunca instalados. `.git/hooks` nao e versionado e nao e
63
72
  * nosso: escrever la dentro sem a pessoa pedir e o mesmo tipo de invasao que
64
73
  * sobrescrever o CLAUDE.md dela. Quem cola, decide.
65
74
  */
66
- const GANCHOS = `
67
- Opcional — o gancho que devolve a memória ao code review:
68
-
69
- .git/hooks/post-commit (avisa quais memórias falam do que você mudou)
70
- #!/bin/sh
71
- dd-harness check --commit "$(git rev-parse HEAD)" || true
72
-
73
- Termina em sucesso mesmo com deriva: avisa, não bloqueia.
74
-
75
- O hook da política (\`dd-harness politica --hook\`) é outra coisa, e não é
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 é
76
85
  opcional — \`dd-harness init\` imprime a linha para o \`.claude/settings.json\`.`;
77
86
  function argumento(argv, nome) {
78
87
  const i = argv.indexOf(`--${nome}`);
@@ -87,14 +96,44 @@ const API_PADRAO = "https://dd-harness.vercel.app";
87
96
  */
88
97
  function instalaMcp() {
89
98
  return new Promise((resolve) => {
99
+ // stdio "inherit": o usuario ve o download do npx acontecendo (pode levar alguns
100
+ // segundos) e, se falhar, ve POR QUE — em vez do "nao consegui" mudo que nao dava
101
+ // pista nenhuma para diagnosticar. O DEP0190 que isto dispara e suprimido no topo
102
+ // do arquivo (`process.removeAllListeners("warning")`).
90
103
  const p = spawn("npx", ["-y", "dd-harness-mcp", "--help"], {
91
104
  shell: process.platform === "win32",
92
- stdio: "ignore",
105
+ stdio: "inherit",
93
106
  });
94
- p.on("exit", (code) => resolve(code === 0));
95
- p.on("error", () => resolve(false));
107
+ p.on("exit", (code) => resolve({ ok: code === 0, erro: code === 0 ? undefined : `código de saída ${code}` }));
108
+ p.on("error", (e) => resolve({ ok: false, erro: e.message }));
96
109
  });
97
110
  }
111
+ /**
112
+ * `start` e o primeiro comando de quem esta chegando — rodar desatualizado aqui e o
113
+ * peor lugar para isso: foi assim que a pre-instalacao do MCP (esta mesma funcao,
114
+ * mais abaixo) ficou muda numa versao anterior que nao tinha essa etapa ainda.
115
+ * Falha de rede aqui nunca trava o wizard — so segue sem avisar.
116
+ */
117
+ async function versaoDesatualizada() {
118
+ try {
119
+ const atual = await versao();
120
+ const controle = new AbortController();
121
+ const corte = setTimeout(() => controle.abort(), 3000);
122
+ const resp = await fetch("https://registry.npmjs.org/dd-harness/latest", {
123
+ signal: controle.signal,
124
+ });
125
+ clearTimeout(corte);
126
+ if (!resp.ok)
127
+ return null;
128
+ const { version: ultima } = (await resp.json());
129
+ if (!ultima || ultima === atual)
130
+ return null;
131
+ return { atual, ultima };
132
+ }
133
+ catch {
134
+ return null;
135
+ }
136
+ }
98
137
  /**
99
138
  * Tenta abrir o navegador padrao. Nunca lanca — falhar em abrir nao pode travar o
100
139
  * wizard, so degrada para "aqui esta a URL, abra voce mesmo".
@@ -112,6 +151,10 @@ function tentaAbrirNavegador(url) {
112
151
  // Segue sem navegador — a URL impressa antes desta chamada ja resolve.
113
152
  }
114
153
  }
154
+ /** Token no terminal aparece mascarado: o prefixo basta para reconhecer, o segredo nao precisa ser lido em voz alta. */
155
+ function mascara(token) {
156
+ return token.length <= 16 ? "***" : `${token.slice(0, 12)}...${token.slice(-4)}`;
157
+ }
115
158
  /**
116
159
  * `dd-harness start` — o wizard para pasta vazia.
117
160
  *
@@ -136,14 +179,37 @@ async function comandoStart() {
136
179
  // ENOENT: exatamente o caso esperado, segue.
137
180
  }
138
181
  console.log("Iniciando o dd-harness nesta pasta.\n");
182
+ // 0. Versao. `start` e o comando de bootstrap: rodar velho aqui significa perder
183
+ // correcoes deste proprio wizard sem nenhum aviso (foi o que aconteceu com a
184
+ // pre-instalacao do MCP, passo 5.5 abaixo, numa versao anterior a ela existir).
185
+ const desatualizado = await versaoDesatualizada();
186
+ if (desatualizado) {
187
+ console.log(`AVISO: você está na versão ${desatualizado.atual}, e a mais recente é ` +
188
+ `${desatualizado.ultima}. Rode \`npm install -g dd-harness@latest\` e comece de ` +
189
+ "novo — start segue mesmo assim, mas pode faltar correção.\n");
190
+ }
139
191
  // 1. Credencial. Sem ela nada do resto e possivel — nem listar tenant.
192
+ // O estado aparece SEMPRE, mesmo quando ja existe credencial: antes o passo era
193
+ // pulado em silencio, e quem rodava `start` com um token velho (ou de outra conta)
194
+ // nao tinha como perceber — o wizard so falhava depois, ao listar espacos.
140
195
  let token = await leToken(API_PADRAO);
196
+ if (token) {
197
+ console.log(`Credencial: ${mascara(token)} (${API_PADRAO})`);
198
+ const troca = await pergunta("Usar esta credencial? [S/n]: ");
199
+ if (/^n/i.test(troca))
200
+ token = null;
201
+ }
141
202
  if (!token) {
142
- console.log(`Sem credencial salva para ${API_PADRAO}.`);
143
- token = await pergunta("Cole o token pessoal (crie um em /tokens na interface): ");
203
+ const url = `${API_PADRAO}/tokens`;
204
+ console.log(`\nCrie um token pessoal em ${url} abrindo no navegador...`);
205
+ tentaAbrirNavegador(url);
206
+ token = await pergunta("Cole o token aqui: ");
207
+ if (!token)
208
+ throw new Error("sem token não dá para seguir: `start` fala com o serviço em todos os passos.");
144
209
  await guardaToken(API_PADRAO, token);
145
- console.log("Credencial guardada.\n");
210
+ console.log("Credencial guardada.");
146
211
  }
212
+ console.log("");
147
213
  // 2. Espaco (tenant). Criar um e ato de dono — nunca por aqui — mas ESCOLHER entre
148
214
  // os que ja existem e so leitura, e e o que faltava para nao exigir o slug de memoria.
149
215
  let tenants = await listaTenants(API_PADRAO, token);
@@ -155,18 +221,20 @@ async function comandoStart() {
155
221
  tenants = await listaTenants(API_PADRAO, token);
156
222
  }
157
223
  const tenant = await escolha("\nEscolha um espaço:", tenants.map((t) => ({ rotulo: `${t.nome} (${t.slug})`, valor: t.slug })));
158
- // 3. Projeto. Criar e subordinado a estar num espaco isso o CLI ja faz sozinho.
224
+ // 3. Projeto. Criar e subordinado a estar num espaco - isso o CLI ja faz sozinho.
159
225
  const nomeSugerido = process.cwd().split(/[\\/]/).pop() ?? "meu-projeto";
160
226
  const nome = (await pergunta(`\nNome do projeto [${nomeSugerido}]: `)) || nomeSugerido;
161
- // Nome so com caracteres especiais (ex: "!!!") normaliza para string vazia o
162
- // fallback evita sugerir slug vazio, que o servidor recusaria com erro confuso.
163
- const slugSugerido = nome
227
+ // O slug sai do nome, sem perguntar: e identificador, nao escolha editorial, e quem
228
+ // esta comecando nao tem como saber que ele vira a chave do `.dd-harness.json`.
229
+ // Nome so com caracteres especiais (ex: "!!!") normaliza para string vazia - o
230
+ // fallback evita slug vazio, que o servidor recusaria com erro confuso.
231
+ const slugDoProjeto = nome
164
232
  .toLowerCase()
165
233
  .normalize("NFD")
166
234
  .replace(/[̀-ͯ]/g, "")
167
235
  .replace(/[^a-z0-9]+/g, "-")
168
236
  .replace(/^-+|-+$/g, "") || "meu-projeto";
169
- const slugDoProjeto = (await pergunta(`Identificador curto (slug) [${slugSugerido}]: `)) || slugSugerido;
237
+ console.log(`Identificador: ${slugDoProjeto}`);
170
238
  const resultadoProjeto = await criaProjeto(process.cwd(), slugDoProjeto, nome, {
171
239
  tenant,
172
240
  api: API_PADRAO,
@@ -204,11 +272,12 @@ async function comandoStart() {
204
272
  }
205
273
  if (mcp.ok) {
206
274
  console.log("\nInstalando o servidor dd-harness-mcp (primeira vez pode demorar um pouco)...");
207
- const instalou = await instalaMcp();
275
+ const { ok: instalou, erro: erroInstalacao } = await instalaMcp();
208
276
  console.log(instalou
209
277
  ? "dd-harness-mcp instalado — a próxima sessão do Claude Code conecta na hora."
210
- : "AVISO: não consegui pré-instalar dd-harness-mcp. A primeira conexão do Claude " +
211
- "Code pode demorar ou falhar; se falhar, abra uma nova sessão e tente de novo.");
278
+ : `AVISO: não consegui pré-instalar dd-harness-mcp (${erroInstalacao}). A primeira ` +
279
+ "conexão do Claude Code pode demorar ou falhar; se falhar, abra uma nova sessão " +
280
+ "e tente de novo.");
212
281
  }
213
282
  // 6. AGENTS.md — so quem usa outra ferramenta alem do Claude Code precisa.
214
283
  const outraFerramenta = await pergunta("\nVai abrir este projeto em Codex, Cursor ou outra ferramenta além do Claude " +
package/package.json CHANGED
@@ -1,44 +1,44 @@
1
- {
2
- "name": "dd-harness",
3
- "version": "0.10.0",
4
- "type": "module",
5
- "description": "Cliente do dd-harness: politica no inicio da sessao, e memoria por busca — nada em disco. Sem dependencia: fetch, crypto e fs sao do Node.",
6
- "license": "UNLICENSED",
7
- "author": "Diego Dias",
8
- "keywords": [
9
- "claude-code",
10
- "ai-agents",
11
- "memory",
12
- "brain",
13
- "cli"
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/cli"
20
- },
21
- "bin": {
22
- "dd-harness": "dist/index.js"
23
- },
24
- "//exports": "Aponta para `dist` porque quem IMPORTA isto em tempo de execucao e o Node, que nao executa TypeScript. Nao aponte para `src`: o Node segue os imports relativos de dentro do arquivo e tenta abrir `./api.js` ao lado do `.ts`, que nao existe. Quem consome no workspace e o `packages/mcp`, e ele compila o fonte do CLI junto (ver o tsconfig.build.json dele) em vez de depender deste campo — assim o build funciona num checkout limpo, sem `dist` previo.",
25
- "exports": {
26
- "./api": "./dist/api.js",
27
- "./buscar": "./dist/buscar.js",
28
- "./curar": "./dist/curar.js",
29
- "./gravar": "./dist/gravar.js",
30
- "./pasta": "./dist/pasta.js",
31
- "./projeto": "./dist/projeto.js"
32
- },
33
- "files": [
34
- "dist"
35
- ],
36
- "engines": {
37
- "node": ">=20"
38
- },
39
- "scripts": {
40
- "typecheck": "tsc -p . --noEmit",
41
- "build": "tsc -p tsconfig.build.json",
42
- "prepublishOnly": "npm run build"
43
- }
44
- }
1
+ {
2
+ "name": "dd-harness",
3
+ "version": "0.12.0",
4
+ "type": "module",
5
+ "description": "Cliente do dd-harness: politica no inicio da sessao, e memoria por busca — nada em disco. Sem dependencia: fetch, crypto e fs sao do Node.",
6
+ "license": "UNLICENSED",
7
+ "author": "Diego Dias",
8
+ "keywords": [
9
+ "claude-code",
10
+ "ai-agents",
11
+ "memory",
12
+ "brain",
13
+ "cli"
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/cli"
20
+ },
21
+ "bin": {
22
+ "dd-harness": "dist/index.js"
23
+ },
24
+ "//exports": "Aponta para `dist` porque quem IMPORTA isto em tempo de execucao e o Node, que nao executa TypeScript. Nao aponte para `src`: o Node segue os imports relativos de dentro do arquivo e tenta abrir `./api.js` ao lado do `.ts`, que nao existe. Quem consome no workspace e o `packages/mcp`, e ele compila o fonte do CLI junto (ver o tsconfig.build.json dele) em vez de depender deste campo — assim o build funciona num checkout limpo, sem `dist` previo.",
25
+ "exports": {
26
+ "./api": "./dist/api.js",
27
+ "./buscar": "./dist/buscar.js",
28
+ "./curar": "./dist/curar.js",
29
+ "./gravar": "./dist/gravar.js",
30
+ "./pasta": "./dist/pasta.js",
31
+ "./projeto": "./dist/projeto.js"
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "engines": {
37
+ "node": ">=20"
38
+ },
39
+ "scripts": {
40
+ "typecheck": "tsc -p . --noEmit",
41
+ "build": "tsc -p tsconfig.build.json",
42
+ "prepublishOnly": "npm run build"
43
+ }
44
+ }