dd-harness 0.3.0 → 0.4.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/brain.d.ts +50 -0
- package/dist/brain.js +9 -0
- package/dist/check.d.ts +1 -1
- package/dist/curar.d.ts +38 -0
- package/dist/curar.js +39 -29
- package/dist/diff.d.ts +1 -1
- package/dist/index.js +120 -119
- package/dist/init.d.ts +25 -4
- package/dist/init.js +58 -47
- package/package.json +44 -44
package/dist/brain.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A forma do Brain no contrato `/api/v1`.
|
|
3
|
+
*
|
|
4
|
+
* So os tipos: o que o servico devolve, e o que o CLI le. Ate a fase 0 este modulo
|
|
5
|
+
* tambem transformava o payload em arquivos de disco — o `sync` materializava politica,
|
|
6
|
+
* briefing e o Brain inteiro no repositorio consumidor. Isso acabou: a politica chega
|
|
7
|
+
* pelo hook de sessao, e a memoria pela busca, na hora. Nada do dd-harness vive em disco.
|
|
8
|
+
*/
|
|
9
|
+
export type Ancora = {
|
|
10
|
+
tipo: string;
|
|
11
|
+
valor: string;
|
|
12
|
+
sha: string | null;
|
|
13
|
+
};
|
|
14
|
+
export type Memoria = {
|
|
15
|
+
pasta: string;
|
|
16
|
+
slug: string;
|
|
17
|
+
titulo: string;
|
|
18
|
+
resumo: string;
|
|
19
|
+
corpo: string;
|
|
20
|
+
status: "ativa" | "historico";
|
|
21
|
+
dano: string;
|
|
22
|
+
invisibilidade: string;
|
|
23
|
+
externalidade: string;
|
|
24
|
+
ancoras: Ancora[];
|
|
25
|
+
revisar_ate: string | null;
|
|
26
|
+
/** Observacoes de deriva esperando julgamento. Ausente em payload antigo. */
|
|
27
|
+
deriva_aberta?: number;
|
|
28
|
+
};
|
|
29
|
+
export type Brain = {
|
|
30
|
+
tenant: {
|
|
31
|
+
slug: string;
|
|
32
|
+
nome: string;
|
|
33
|
+
};
|
|
34
|
+
projeto: {
|
|
35
|
+
slug: string;
|
|
36
|
+
nome: string;
|
|
37
|
+
};
|
|
38
|
+
politica?: string | null;
|
|
39
|
+
briefing?: string | null;
|
|
40
|
+
pastas: {
|
|
41
|
+
slug: string;
|
|
42
|
+
definicao: string;
|
|
43
|
+
}[];
|
|
44
|
+
memorias: Memoria[];
|
|
45
|
+
/**
|
|
46
|
+
* Memorias que excederam as tentativas de indexacao e nao serao mais tentadas. Ficam
|
|
47
|
+
* sem embedding — somem da busca semantica — e so este numero denuncia.
|
|
48
|
+
*/
|
|
49
|
+
travadas_na_fila?: number;
|
|
50
|
+
};
|
package/dist/brain.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A forma do Brain no contrato `/api/v1`.
|
|
3
|
+
*
|
|
4
|
+
* So os tipos: o que o servico devolve, e o que o CLI le. Ate a fase 0 este modulo
|
|
5
|
+
* tambem transformava o payload em arquivos de disco — o `sync` materializava politica,
|
|
6
|
+
* briefing e o Brain inteiro no repositorio consumidor. Isso acabou: a politica chega
|
|
7
|
+
* pelo hook de sessao, e a memoria pela busca, na hora. Nada do dd-harness vive em disco.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
package/dist/check.d.ts
CHANGED
package/dist/curar.d.ts
CHANGED
|
@@ -1,3 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curadoria pelo agente: ler, editar e arquivar.
|
|
3
|
+
*
|
|
4
|
+
* `gravar` sabia criar e mais nada. Uma memoria errada ficava errada, porque corrigir
|
|
5
|
+
* exigia abrir a interface — e o `CLAUDE.md` trata curadoria como obrigacao ("se
|
|
6
|
+
* encontrar uma memoria obsoleta ou errada, corrija").
|
|
7
|
+
*
|
|
8
|
+
* Editar reaproveita o mesmo markdown de `gravar`: um formato so para as duas operacoes.
|
|
9
|
+
* Ate a fase 0 o ponto de partida era o arquivo que o `sync` materializava; agora e o
|
|
10
|
+
* `le`, que busca a memoria no servico e devolve nesse mesmo formato.
|
|
11
|
+
*/
|
|
12
|
+
export type MemoriaDoServico = {
|
|
13
|
+
pasta: string;
|
|
14
|
+
slug: string;
|
|
15
|
+
titulo: string;
|
|
16
|
+
resumo: string;
|
|
17
|
+
corpo: string;
|
|
18
|
+
status: string;
|
|
19
|
+
dano: string;
|
|
20
|
+
invisibilidade: string;
|
|
21
|
+
externalidade: string;
|
|
22
|
+
revisar_ate: string | null;
|
|
23
|
+
ancoras: {
|
|
24
|
+
tipo: string;
|
|
25
|
+
valor: string;
|
|
26
|
+
sha: string | null;
|
|
27
|
+
}[];
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* A memoria inteira, no markdown que `gravar` e `editar` consomem.
|
|
31
|
+
*
|
|
32
|
+
* Sem materializacao, esta e a unica forma de ler o corpo: a busca devolve so endereco,
|
|
33
|
+
* titulo e resumo, e o disco nao tem mais nada. Tambem e o ponto de partida de qualquer
|
|
34
|
+
* edicao — corrigir exige ver o que esta la.
|
|
35
|
+
*/
|
|
36
|
+
export declare function le(raiz: string, endereco: string): Promise<string>;
|
|
37
|
+
/** O formato canonico: o mesmo que `interpreta` le, para o ciclo fechar sem conversao. */
|
|
38
|
+
export declare function comoMarkdown(m: MemoriaDoServico): string;
|
|
1
39
|
export declare function edita(raiz: string, caminho: string): Promise<{
|
|
2
40
|
endereco: string;
|
|
3
41
|
ancoras: number;
|
package/dist/curar.js
CHANGED
|
@@ -1,37 +1,48 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { relative } from "node:path";
|
|
3
2
|
import { cabecalhos, credencial, pede, recusa } from "./api.js";
|
|
4
|
-
import { gravaManifesto, hashDe, leManifesto } from "./config.js";
|
|
5
3
|
import { interpreta } from "./gravar.js";
|
|
6
4
|
/**
|
|
7
|
-
*
|
|
5
|
+
* A memoria inteira, no markdown que `gravar` e `editar` consomem.
|
|
8
6
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Editar reaproveita o mesmo markdown de `gravar`, de proposito: o agente edita o arquivo
|
|
14
|
-
* que o `sync` materializou e manda de volta. Um formato so para as duas operacoes, e o
|
|
15
|
-
* que ele ja sabe ler.
|
|
16
|
-
*/
|
|
17
|
-
/**
|
|
18
|
-
* Depois que o servico aceita, o arquivo em disco deixa de ser "edicao nao enviada" — e o
|
|
19
|
-
* manifesto tem que saber, senao o `sync` seguinte para com "editado a mao" e a unica saida
|
|
20
|
-
* que resta e descartar o arquivo. O ciclo materializa-corrige-envia travava justamente
|
|
21
|
-
* aqui, com o servico ja atualizado.
|
|
22
|
-
*
|
|
23
|
-
* Guarda o hash do que foi enviado, nao do que o servico devolveria: o `sync` seguinte
|
|
24
|
-
* reescreve o arquivo na forma canonica quando o ETag mudar.
|
|
7
|
+
* Sem materializacao, esta e a unica forma de ler o corpo: a busca devolve so endereco,
|
|
8
|
+
* titulo e resumo, e o disco nao tem mais nada. Tambem e o ponto de partida de qualquer
|
|
9
|
+
* edicao — corrigir exige ver o que esta la.
|
|
25
10
|
*/
|
|
26
|
-
async function
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
await
|
|
11
|
+
export async function le(raiz, endereco) {
|
|
12
|
+
const { config, token } = await credencial(raiz);
|
|
13
|
+
const url = new URL(`${config.api}/api/v1/memorias/${endereco}`);
|
|
14
|
+
url.searchParams.set("tenant", config.tenant);
|
|
15
|
+
url.searchParams.set("projeto", config.projeto);
|
|
16
|
+
const resposta = await pede(url, { headers: cabecalhos(token) });
|
|
17
|
+
if (!resposta.ok)
|
|
18
|
+
await recusa(resposta);
|
|
19
|
+
return comoMarkdown((await resposta.json()));
|
|
20
|
+
}
|
|
21
|
+
/** O formato canonico: o mesmo que `interpreta` le, para o ciclo fechar sem conversao. */
|
|
22
|
+
export function comoMarkdown(m) {
|
|
23
|
+
const ancoras = m.ancoras.length
|
|
24
|
+
? `\n## Âncoras\n\n${m.ancoras.map((a) => `- \`${a.valor}\``).join("\n")}\n`
|
|
25
|
+
: "";
|
|
26
|
+
return [
|
|
27
|
+
"---",
|
|
28
|
+
`name: ${m.slug}`,
|
|
29
|
+
`titulo: ${m.titulo}`,
|
|
30
|
+
`description: ${m.resumo}`,
|
|
31
|
+
`pasta: ${m.pasta}`,
|
|
32
|
+
...(m.revisar_ate ? [`revisar-ate: ${m.revisar_ate.slice(0, 10)}`] : []),
|
|
33
|
+
"---",
|
|
34
|
+
"",
|
|
35
|
+
m.corpo.trim(),
|
|
36
|
+
"",
|
|
37
|
+
"## Os três filtros",
|
|
38
|
+
"",
|
|
39
|
+
`**Dano:** ${m.dano}`,
|
|
40
|
+
"",
|
|
41
|
+
`**Invisibilidade:** ${m.invisibilidade}`,
|
|
42
|
+
"",
|
|
43
|
+
`**Externalidade:** ${m.externalidade}`,
|
|
44
|
+
ancoras,
|
|
45
|
+
].join("\n");
|
|
35
46
|
}
|
|
36
47
|
export async function edita(raiz, caminho) {
|
|
37
48
|
const { config, token } = await credencial(raiz);
|
|
@@ -55,7 +66,6 @@ export async function edita(raiz, caminho) {
|
|
|
55
66
|
});
|
|
56
67
|
if (!resposta.ok)
|
|
57
68
|
await recusa(resposta);
|
|
58
|
-
await marcaComoEnviado(raiz, caminho, cru);
|
|
59
69
|
return { endereco, ancoras: memoria.ancoras.length };
|
|
60
70
|
}
|
|
61
71
|
export async function arquiva(raiz, endereco, opcoes) {
|
package/dist/diff.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -3,14 +3,12 @@ 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_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";
|
|
14
12
|
/**
|
|
15
13
|
* `dd-harness` — o cliente que materializa os artefatos no repositorio.
|
|
16
14
|
*
|
|
@@ -19,80 +17,51 @@ import { sync } from "./sync.js";
|
|
|
19
17
|
* em repositorio alheio tem que envelhecer bem, e cada dependencia e uma chance de nao
|
|
20
18
|
* envelhecer.
|
|
21
19
|
*/
|
|
22
|
-
const AJUDA = `dd-harness —
|
|
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
|
|
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
|
|
42
|
-
saída 0 = veio; 3 = projeto sem política;
|
|
43
|
-
1 = não consegui buscar
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
20
|
+
const AJUDA = `dd-harness — a política e o Brain do projeto, no serviço
|
|
21
|
+
|
|
22
|
+
dd-harness login --token <token> [--api <url>]
|
|
23
|
+
guarda a credencial desta máquina
|
|
24
|
+
dd-harness projeto <slug> --nome "<nome>" [--tenant <t>] [--api <url>]
|
|
25
|
+
cria o projeto no serviço (antes do init)
|
|
26
|
+
dd-harness init --tenant <t> --projeto <p> [--api <url>]
|
|
27
|
+
prepara o repositório (config + CLAUDE.md)
|
|
28
|
+
dd-harness pasta <slug> --definicao "o que entra e o que não entra"
|
|
29
|
+
cria a pasta que o gravar exige
|
|
30
|
+
dd-harness gravar <arquivo.md> registra uma memória a partir de um markdown
|
|
31
|
+
dd-harness editar <arquivo.md> corrige o que já está gravado
|
|
32
|
+
dd-harness arquivar <pasta>/<slug> --motivo <obsoleta|incorreta|fora_dos_filtros>
|
|
33
|
+
[--substituida-por <pasta>/<slug>]
|
|
34
|
+
tira de circulação sem apagar
|
|
35
|
+
dd-harness buscar "<pergunta>" acha memória por relevância, não por arquivo
|
|
36
|
+
dd-harness ler <pasta>/<slug> imprime a memória inteira, no formato de gravar
|
|
37
|
+
dd-harness check [--commit <sha>] mede as âncoras e reporta a deriva
|
|
38
|
+
dd-harness status só lê: o tamanho do Brain e o que espera julgamento
|
|
39
|
+
dd-harness politica [--hook] imprime a política do serviço
|
|
40
|
+
saída 0 = veio; 3 = projeto sem política;
|
|
41
|
+
1 = não consegui buscar
|
|
42
|
+
--hook: fala o protocolo do SessionStart do
|
|
43
|
+
Claude Code, para pôr a política no contexto
|
|
44
|
+
dd-harness --help
|
|
45
|
+
|
|
46
|
+
Nada do dd-harness fica em disco: a política chega pelo hook de sessão, e a
|
|
47
|
+
memória pela busca, na hora.
|
|
48
48
|
`;
|
|
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
49
|
/**
|
|
80
50
|
* Os ganchos sao IMPRESSOS, nunca instalados. `.git/hooks` nao e versionado e nao e
|
|
81
51
|
* nosso: escrever la dentro sem a pessoa pedir e o mesmo tipo de invasao que
|
|
82
52
|
* sobrescrever o CLAUDE.md dela. Quem cola, decide.
|
|
83
53
|
*/
|
|
84
|
-
const GANCHOS = `
|
|
85
|
-
Opcional —
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
Os dois terminam em sucesso mesmo com deriva: avisam, não bloqueiam.`;
|
|
54
|
+
const GANCHOS = `
|
|
55
|
+
Opcional — o gancho que devolve a memória ao code review:
|
|
56
|
+
|
|
57
|
+
.git/hooks/post-commit (avisa quais memórias falam do que você mudou)
|
|
58
|
+
#!/bin/sh
|
|
59
|
+
dd-harness check --commit "$(git rev-parse HEAD)" || true
|
|
60
|
+
|
|
61
|
+
Termina em sucesso mesmo com deriva: avisa, não bloqueia.
|
|
62
|
+
|
|
63
|
+
O hook da política (\`dd-harness politica --hook\`) é outra coisa, e não é
|
|
64
|
+
opcional — \`dd-harness init\` imprime a linha para o \`.claude/settings.json\`.`;
|
|
96
65
|
function argumento(argv, nome) {
|
|
97
66
|
const i = argv.indexOf(`--${nome}`);
|
|
98
67
|
return i >= 0 ? argv[i + 1] : undefined;
|
|
@@ -109,12 +78,19 @@ async function comandoInit(argv) {
|
|
|
109
78
|
api: argumento(argv, "api"),
|
|
110
79
|
});
|
|
111
80
|
console.log(r.config === "criada" ? "criado .dd-harness.json" : "mantido .dd-harness.json");
|
|
112
|
-
console.log(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
81
|
+
console.log("\nAgora: dd-harness login --token <token>");
|
|
82
|
+
// O hook vem primeiro e nao e opcional: sem ele a sessao abre sem politica, que e a
|
|
83
|
+
// falha que este projeto existe para combater. O MCP e conveniencia; este, nao.
|
|
84
|
+
if (r.hook === "ja-declarado") {
|
|
85
|
+
console.log("\nmantido .claude/settings.json — o hook da política já está declarado");
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
console.log("\nOBRIGATÓRIO: o hook que carrega a política no início de cada sessão." +
|
|
89
|
+
"\nSem ele a sessão abre sem protocolo, e nada avisa. Acrescente ao" +
|
|
90
|
+
"\n`.claude/settings.json` (não escrevo nele: o arquivo é seu e pode já" +
|
|
91
|
+
"\nter hooks e permissões):\n");
|
|
92
|
+
console.log(SUGESTAO_HOOK);
|
|
93
|
+
}
|
|
118
94
|
if (r.mcp === "ja-declarado") {
|
|
119
95
|
console.log("\nmantido .mcp.json — o servidor dd-harness já está declarado");
|
|
120
96
|
return;
|
|
@@ -147,7 +123,7 @@ async function comandoEditar(argv) {
|
|
|
147
123
|
}
|
|
148
124
|
const r = await edita(process.cwd(), caminho);
|
|
149
125
|
console.log(`editado ${r.endereco}`);
|
|
150
|
-
console.log(` ${r.ancoras} âncora(s)
|
|
126
|
+
console.log(` ${r.ancoras} âncora(s).`);
|
|
151
127
|
}
|
|
152
128
|
const MOTIVOS = ["obsoleta", "incorreta", "fora_dos_filtros"];
|
|
153
129
|
async function comandoArquivar(argv) {
|
|
@@ -166,7 +142,20 @@ async function comandoArquivar(argv) {
|
|
|
166
142
|
substituidaPor: argumento(argv, "substituida-por"),
|
|
167
143
|
});
|
|
168
144
|
console.log(`arquivado ${r.endereco} (${r.motivo})`);
|
|
169
|
-
console.log(" foi para o histórico, não foi apagada.
|
|
145
|
+
console.log(" foi para o histórico, não foi apagada.");
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* A memoria inteira no stdout, no mesmo markdown que `gravar` e `editar` consomem.
|
|
149
|
+
*
|
|
150
|
+
* Sem materializacao este e o unico caminho para o corpo: a busca devolve so endereco,
|
|
151
|
+
* titulo e resumo. Tambem e o ponto de partida de toda edicao — corrigir exige ver.
|
|
152
|
+
*/
|
|
153
|
+
async function comandoLer(argv) {
|
|
154
|
+
const endereco = argv[0];
|
|
155
|
+
if (!endereco || endereco.startsWith("-")) {
|
|
156
|
+
throw new Error("uso: dd-harness ler <pasta>/<slug>");
|
|
157
|
+
}
|
|
158
|
+
console.log(await le(process.cwd(), endereco));
|
|
170
159
|
}
|
|
171
160
|
async function comandoBuscar(argv) {
|
|
172
161
|
const consulta = termosDaConsulta(argv);
|
|
@@ -191,36 +180,6 @@ async function comandoBuscar(argv) {
|
|
|
191
180
|
console.log(` ${a.resumo}`);
|
|
192
181
|
}
|
|
193
182
|
}
|
|
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;
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
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);
|
|
223
|
-
}
|
|
224
183
|
async function comandoCheck(argv) {
|
|
225
184
|
const r = await check(process.cwd(), argumento(argv, "commit"));
|
|
226
185
|
if (r.medidas === 0) {
|
|
@@ -273,8 +232,16 @@ async function comandoCheck(argv) {
|
|
|
273
232
|
* um "falhou" generico colapsaria: projeto novo (que precisa de briefing) de politica
|
|
274
233
|
* inalcancavel (que precisa PARAR a sessao). Mudar estes numeros quebra o hook.
|
|
275
234
|
*/
|
|
276
|
-
async function comandoPolitica() {
|
|
235
|
+
async function comandoPolitica(argv) {
|
|
277
236
|
const r = await buscaPolitica(process.cwd());
|
|
237
|
+
// `--hook`: fala o protocolo do SessionStart do Claude Code, que injeta
|
|
238
|
+
// `additionalContext` no contexto da sessao. Sem a flag, saida legivel para quem roda
|
|
239
|
+
// no terminal. A diferenca importa: o hook precisa que o AVISO chegue ao modelo, e
|
|
240
|
+
// stderr so chega ao transcript — aviso que o modelo nao le e o mesmo que silencio.
|
|
241
|
+
if (argv.includes("--hook")) {
|
|
242
|
+
console.log(JSON.stringify({ hookSpecificOutput: contextoDaSessao(r) }));
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
278
245
|
if (r.estado === "ok") {
|
|
279
246
|
console.log(r.conteudo);
|
|
280
247
|
return;
|
|
@@ -287,6 +254,42 @@ async function comandoPolitica() {
|
|
|
287
254
|
console.error(`não consegui buscar a política: ${r.motivo}`);
|
|
288
255
|
process.exitCode = 1;
|
|
289
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* O que o hook injeta no contexto, por estado.
|
|
259
|
+
*
|
|
260
|
+
* Sai sempre com codigo 0: o que precisa chegar ao modelo e o TEXTO, e um codigo de erro
|
|
261
|
+
* so faria o Claude Code registrar falha no transcript — que ninguem le — enquanto a
|
|
262
|
+
* sessao seguiria sem saber que esta sem protocolo.
|
|
263
|
+
*/
|
|
264
|
+
function contextoDaSessao(r) {
|
|
265
|
+
const base = { hookEventName: "SessionStart" };
|
|
266
|
+
if (r.estado === "ok") {
|
|
267
|
+
return {
|
|
268
|
+
...base,
|
|
269
|
+
additionalContext: "# Política deste projeto (carregada do dd-harness)\n\n" +
|
|
270
|
+
"As regras abaixo valem para esta sessão inteira.\n\n" +
|
|
271
|
+
r.conteudo,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
if (r.estado === "sem-politica") {
|
|
275
|
+
return {
|
|
276
|
+
...base,
|
|
277
|
+
additionalContext: "AVISO DO DD-HARNESS: este projeto existe no serviço mas **nunca foi briefado** " +
|
|
278
|
+
"— não há política.\n\nIsto não é uma falha: é um projeto novo. Antes de " +
|
|
279
|
+
"implementar qualquer coisa, diga isso ao usuário e proponha rodar `/briefar`.",
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
...base,
|
|
284
|
+
additionalContext: "PARE: NÃO FOI POSSÍVEL CARREGAR A POLÍTICA DESTE PROJETO.\n\n" +
|
|
285
|
+
`Motivo: ${r.motivo}\n\n` +
|
|
286
|
+
"A política pode existir no serviço e não ter chegado até aqui, então esta sessão " +
|
|
287
|
+
"está **sem protocolo** — as proibições e a regra do OK não foram carregadas.\n\n" +
|
|
288
|
+
"Antes de qualquer outra coisa: avise o usuário com estas palavras e **não " +
|
|
289
|
+
"modifique nenhum arquivo** até ele decidir como prosseguir. Seguir como se nada " +
|
|
290
|
+
"tivesse acontecido é exatamente a falha que este projeto combate.",
|
|
291
|
+
};
|
|
292
|
+
}
|
|
290
293
|
async function comandoStatus() {
|
|
291
294
|
const r = await status(process.cwd());
|
|
292
295
|
// O acervo vem primeiro, e sempre: e a resposta para "o que ha no Brain deste projeto?",
|
|
@@ -361,9 +364,8 @@ async function comandoPasta(argv) {
|
|
|
361
364
|
}
|
|
362
365
|
/**
|
|
363
366
|
* 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
|
|
365
|
-
*
|
|
366
|
-
* lado da copia gerada.
|
|
367
|
+
* comando o transforma em requisicao. O arquivo e so o veiculo: depois de gravado, a
|
|
368
|
+
* memoria vive no servico, e quem quiser le-la usa a busca. Nada fica em disco.
|
|
367
369
|
*/
|
|
368
370
|
async function comandoGravar(argv) {
|
|
369
371
|
const caminho = argv[0];
|
|
@@ -372,8 +374,7 @@ async function comandoGravar(argv) {
|
|
|
372
374
|
}
|
|
373
375
|
const r = await grava(process.cwd(), caminho);
|
|
374
376
|
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.");
|
|
377
|
+
console.log(` ${r.ancoras} âncora(s), valendo para ${r.projetos} projeto(s).`);
|
|
377
378
|
}
|
|
378
379
|
async function principal() {
|
|
379
380
|
const [comando, ...resto] = process.argv.slice(2);
|
|
@@ -392,16 +393,16 @@ async function principal() {
|
|
|
392
393
|
return comandoEditar(resto);
|
|
393
394
|
case "arquivar":
|
|
394
395
|
return comandoArquivar(resto);
|
|
396
|
+
case "ler":
|
|
397
|
+
return comandoLer(resto);
|
|
395
398
|
case "buscar":
|
|
396
399
|
return comandoBuscar(resto);
|
|
397
|
-
case "sync":
|
|
398
|
-
return comandoSync();
|
|
399
400
|
case "check":
|
|
400
401
|
return comandoCheck(resto);
|
|
401
402
|
case "status":
|
|
402
403
|
return comandoStatus();
|
|
403
404
|
case "politica":
|
|
404
|
-
return comandoPolitica();
|
|
405
|
+
return comandoPolitica(resto);
|
|
405
406
|
case "--help":
|
|
406
407
|
case "-h":
|
|
407
408
|
case undefined:
|
package/dist/init.d.ts
CHANGED
|
@@ -1,16 +1,37 @@
|
|
|
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";
|
|
5
15
|
};
|
|
6
16
|
/**
|
|
7
17
|
* O `.mcp.json` e sugerido, nunca escrito.
|
|
8
18
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
19
|
+
* O arquivo e do repositorio, pode ja declarar outros servidores, e mesclar JSON alheio e
|
|
20
|
+
* onde falha silenciosa nasce — entrada errada nao da erro, a ferramenta so nao aparece.
|
|
21
|
+
* Quem cola sabe o que colou.
|
|
12
22
|
*/
|
|
13
23
|
export declare const SUGESTAO_MCP = "{\n \"mcpServers\": {\n \"dd-harness\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"dd-harness-mcp\"]\n }\n }\n}";
|
|
24
|
+
/**
|
|
25
|
+
* O hook que carrega a politica no inicio de cada sessao.
|
|
26
|
+
*
|
|
27
|
+
* E a garantia de que nenhuma sessao abre sem protocolo — o papel que antes era do
|
|
28
|
+
* arquivo materializado mais a linha de import. Vive num hook, e nao numa instrucao no
|
|
29
|
+
* `CLAUDE.md`, porque instrucao o modelo pode pular: o import quebrado falhava em
|
|
30
|
+
* silencio, e isso foi medido.
|
|
31
|
+
*
|
|
32
|
+
* Sugerido e nao escrito, pelo mesmo motivo do `.mcp.json`.
|
|
33
|
+
*/
|
|
34
|
+
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}";
|
|
14
35
|
export declare function init(raiz: string, dados: {
|
|
15
36
|
tenant: string;
|
|
16
37
|
projeto: string;
|
package/dist/init.js
CHANGED
|
@@ -1,36 +1,45 @@
|
|
|
1
1
|
import { readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { CAMINHO_CONFIG } from "./config.js";
|
|
4
|
-
import { LINHA_DE_IMPORT } from "./materializa.js";
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
5
|
+
* O `.mcp.json` e sugerido, nunca escrito.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* uma linha, e nao um ritual de mover arquivo.
|
|
7
|
+
* O arquivo e do repositorio, pode ja declarar outros servidores, e mesclar JSON alheio e
|
|
8
|
+
* onde falha silenciosa nasce — entrada errada nao da erro, a ferramenta so nao aparece.
|
|
9
|
+
* Quem cola sabe o que colou.
|
|
12
10
|
*/
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
export const SUGESTAO_MCP = `{
|
|
12
|
+
"mcpServers": {
|
|
13
|
+
"dd-harness": {
|
|
14
|
+
"command": "npx",
|
|
15
|
+
"args": ["-y", "dd-harness-mcp"]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}`;
|
|
20
19
|
/**
|
|
21
|
-
* O
|
|
20
|
+
* O hook que carrega a politica no inicio de cada sessao.
|
|
21
|
+
*
|
|
22
|
+
* E a garantia de que nenhuma sessao abre sem protocolo — o papel que antes era do
|
|
23
|
+
* arquivo materializado mais a linha de import. Vive num hook, e nao numa instrucao no
|
|
24
|
+
* `CLAUDE.md`, porque instrucao o modelo pode pular: o import quebrado falhava em
|
|
25
|
+
* silencio, e isso foi medido.
|
|
22
26
|
*
|
|
23
|
-
*
|
|
24
|
-
* outros servidores, e mesclar JSON alheio e onde falha silenciosa nasce — entrada errada
|
|
25
|
-
* nao da erro, a ferramenta so nao aparece. Quem cola sabe o que colou.
|
|
27
|
+
* Sugerido e nao escrito, pelo mesmo motivo do `.mcp.json`.
|
|
26
28
|
*/
|
|
27
|
-
export const
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
export const SUGESTAO_HOOK = `{
|
|
30
|
+
"hooks": {
|
|
31
|
+
"SessionStart": [
|
|
32
|
+
{
|
|
33
|
+
"hooks": [
|
|
34
|
+
{
|
|
35
|
+
"type": "command",
|
|
36
|
+
"command": "dd-harness politica --hook",
|
|
37
|
+
"statusMessage": "Carregando a política do dd-harness..."
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
34
43
|
}`;
|
|
35
44
|
async function declaraMcp(raiz) {
|
|
36
45
|
try {
|
|
@@ -43,6 +52,26 @@ async function declaraMcp(raiz) {
|
|
|
43
52
|
return "a-declarar";
|
|
44
53
|
}
|
|
45
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* O hook ja esta declarado?
|
|
57
|
+
*
|
|
58
|
+
* Procura pelo COMANDO, nao pela forma: `settings.json` aceita varios formatos de
|
|
59
|
+
* matcher, e quem ja tem o hook pode te-lo escrito de outro jeito. O que importa e se
|
|
60
|
+
* `dd-harness politica` roda no inicio da sessao.
|
|
61
|
+
*/
|
|
62
|
+
async function declaraHook(raiz) {
|
|
63
|
+
for (const arquivo of [".claude/settings.json", ".claude/settings.local.json"]) {
|
|
64
|
+
try {
|
|
65
|
+
const cru = await readFile(join(raiz, arquivo), "utf8");
|
|
66
|
+
if (cru.includes("dd-harness politica"))
|
|
67
|
+
return "ja-declarado";
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Sem arquivo: segue para o proximo.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return "a-declarar";
|
|
74
|
+
}
|
|
46
75
|
export async function init(raiz, dados) {
|
|
47
76
|
const caminhoConfig = join(raiz, CAMINHO_CONFIG);
|
|
48
77
|
let config = "ja-existia";
|
|
@@ -58,27 +87,9 @@ export async function init(raiz, dados) {
|
|
|
58
87
|
await writeFile(caminhoConfig, `${JSON.stringify(conteudo, null, 2)}\n`, "utf8");
|
|
59
88
|
config = "criada";
|
|
60
89
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
atual = null;
|
|
69
|
-
}
|
|
70
|
-
if (atual === null) {
|
|
71
|
-
await writeFile(caminhoClaude, `${CABECALHO}\n${LINHA_DE_IMPORT}\n`, "utf8");
|
|
72
|
-
claudeMd = "criado";
|
|
73
|
-
}
|
|
74
|
-
else if (atual.includes(LINHA_DE_IMPORT)) {
|
|
75
|
-
claudeMd = "ja-tinha-a-linha";
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
// Acrescenta no fim, sem reescrever nada do que ja estava la.
|
|
79
|
-
const separador = atual.endsWith("\n") ? "\n" : "\n\n";
|
|
80
|
-
await writeFile(caminhoClaude, `${atual}${separador}${LINHA_DE_IMPORT}\n`, "utf8");
|
|
81
|
-
claudeMd = "linha-acrescentada";
|
|
82
|
-
}
|
|
83
|
-
return { config, claudeMd, mcp: await declaraMcp(raiz) };
|
|
90
|
+
return {
|
|
91
|
+
config,
|
|
92
|
+
mcp: await declaraMcp(raiz),
|
|
93
|
+
hook: await declaraHook(raiz),
|
|
94
|
+
};
|
|
84
95
|
}
|
package/package.json
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dd-harness",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "
|
|
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.4.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
|
+
}
|