dd-harness 0.2.0 → 0.3.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/LICENSE +11 -11
- package/README.md +64 -64
- package/dist/argv.d.ts +14 -0
- package/dist/argv.js +27 -0
- package/dist/artefato.d.ts +30 -0
- package/dist/artefato.js +42 -0
- package/dist/check.d.ts +5 -0
- package/dist/check.js +1 -0
- package/dist/diff.d.ts +5 -0
- package/dist/diff.js +7 -1
- package/dist/gravar.js +11 -2
- package/dist/index.js +71 -35
- package/dist/init.js +13 -13
- package/dist/materializa.d.ts +5 -0
- package/dist/politica.d.ts +27 -0
- package/dist/politica.js +35 -0
- package/package.json +44 -44
package/LICENSE
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
Copyright (c) 2026 Diego Dias
|
|
2
|
-
|
|
3
|
-
Todos os direitos reservados.
|
|
4
|
-
|
|
5
|
-
Este software é publicado no registro npm apenas para distribuição ao seu autor e a
|
|
6
|
-
quem ele autorizar expressamente. Nenhuma permissão de uso, cópia, modificação,
|
|
7
|
-
distribuição ou criação de obras derivadas é concedida por esta publicação.
|
|
8
|
-
|
|
9
|
-
All rights reserved. This software is published to the npm registry for distribution
|
|
10
|
-
to its author and to those he expressly authorizes. No permission to use, copy,
|
|
11
|
-
modify, distribute, or create derivative works is granted by this publication.
|
|
1
|
+
Copyright (c) 2026 Diego Dias
|
|
2
|
+
|
|
3
|
+
Todos os direitos reservados.
|
|
4
|
+
|
|
5
|
+
Este software é publicado no registro npm apenas para distribuição ao seu autor e a
|
|
6
|
+
quem ele autorizar expressamente. Nenhuma permissão de uso, cópia, modificação,
|
|
7
|
+
distribuição ou criação de obras derivadas é concedida por esta publicação.
|
|
8
|
+
|
|
9
|
+
All rights reserved. This software is published to the npm registry for distribution
|
|
10
|
+
to its author and to those he expressly authorizes. No permission to use, copy,
|
|
11
|
+
modify, distribute, or create derivative works is granted by this publication.
|
package/README.md
CHANGED
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
# dd-harness
|
|
2
|
-
|
|
3
|
-
Materializa a **política**, o **briefing** e o **Brain** do
|
|
4
|
-
[dd-harness](https://dd-harness.vercel.app) dentro do seu repositório, para que a sessão
|
|
5
|
-
do agente de código abra já sabendo as regras e as decisões do projeto.
|
|
6
|
-
|
|
7
|
-
Sem dependência: `fetch`, `crypto` e `fs` são do Node. Um CLI que vive pinado em
|
|
8
|
-
repositório alheio precisa envelhecer bem, e cada dependência é uma chance de não
|
|
9
|
-
envelhecer.
|
|
10
|
-
|
|
11
|
-
## Instalação
|
|
12
|
-
|
|
13
|
-
```sh
|
|
14
|
-
npm install -g dd-harness
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Ou sem instalar nada:
|
|
18
|
-
|
|
19
|
-
```sh
|
|
20
|
-
npx dd-harness sync
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Requer Node 20 ou mais novo.
|
|
24
|
-
|
|
25
|
-
## Uso
|
|
26
|
-
|
|
27
|
-
```sh
|
|
28
|
-
dd-harness init --tenant <espaço> --projeto <projeto> # prepara o repositório
|
|
29
|
-
dd-harness login --token <token> # credencial desta máquina
|
|
30
|
-
dd-harness sync # escreve os artefatos
|
|
31
|
-
dd-harness check [--commit <sha>] # mede âncoras e reporta deriva
|
|
32
|
-
dd-harness status # só lê: o que espera julgamento
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
O token pessoal nasce na tela `/tokens` do serviço. Ele é guardado em
|
|
36
|
-
`~/.dd-harness/credentials.json` (modo `0600`, por origem de API) — **fora** do
|
|
37
|
-
repositório, para não viajar num commit.
|
|
38
|
-
|
|
39
|
-
## O que aparece no seu repositório
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
.dd-harness.json configuração (tenant, projeto, api) — escrita à mão
|
|
43
|
-
CLAUDE.md SEU arquivo; o sync nunca o reescreve
|
|
44
|
-
dd-harness/ tudo o que é gerado
|
|
45
|
-
politica.md
|
|
46
|
-
BRIEFING.md
|
|
47
|
-
brain/
|
|
48
|
-
manifest.json
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Na raiz fica apenas o **seu** `CLAUDE.md`, que importa a política com a linha
|
|
52
|
-
`@dd-harness/politica.md`. Isso deixa conviverem a parte gerenciada e o que o
|
|
53
|
-
repositório tem de próprio — adotar um projeto existente é acrescentar uma linha, não
|
|
54
|
-
sobrescrever nada.
|
|
55
|
-
|
|
56
|
-
O `sync` confere esse ponteiro a cada execução, inclusive quando o serviço não mudou:
|
|
57
|
-
import quebrado ou linha ausente faz a sessão abrir **sem política e sem avisar**. Isso
|
|
58
|
-
foi medido, não suposto.
|
|
59
|
-
|
|
60
|
-
## Deriva
|
|
61
|
-
|
|
62
|
-
`dd-harness check` mede as âncoras das memórias contra o estado real do repositório e
|
|
63
|
-
reporta o que saiu do lugar. Com `--commit <sha>` ele também cruza as âncoras com o diff
|
|
64
|
-
daquele commit — a memória volta ao code review. Nada bloqueia: avisa.
|
|
1
|
+
# dd-harness
|
|
2
|
+
|
|
3
|
+
Materializa a **política**, o **briefing** e o **Brain** do
|
|
4
|
+
[dd-harness](https://dd-harness.vercel.app) dentro do seu repositório, para que a sessão
|
|
5
|
+
do agente de código abra já sabendo as regras e as decisões do projeto.
|
|
6
|
+
|
|
7
|
+
Sem dependência: `fetch`, `crypto` e `fs` são do Node. Um CLI que vive pinado em
|
|
8
|
+
repositório alheio precisa envelhecer bem, e cada dependência é uma chance de não
|
|
9
|
+
envelhecer.
|
|
10
|
+
|
|
11
|
+
## Instalação
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install -g dd-harness
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Ou sem instalar nada:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npx dd-harness sync
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Requer Node 20 ou mais novo.
|
|
24
|
+
|
|
25
|
+
## Uso
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
dd-harness init --tenant <espaço> --projeto <projeto> # prepara o repositório
|
|
29
|
+
dd-harness login --token <token> # credencial desta máquina
|
|
30
|
+
dd-harness sync # escreve os artefatos
|
|
31
|
+
dd-harness check [--commit <sha>] # mede âncoras e reporta deriva
|
|
32
|
+
dd-harness status # só lê: o que espera julgamento
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
O token pessoal nasce na tela `/tokens` do serviço. Ele é guardado em
|
|
36
|
+
`~/.dd-harness/credentials.json` (modo `0600`, por origem de API) — **fora** do
|
|
37
|
+
repositório, para não viajar num commit.
|
|
38
|
+
|
|
39
|
+
## O que aparece no seu repositório
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
.dd-harness.json configuração (tenant, projeto, api) — escrita à mão
|
|
43
|
+
CLAUDE.md SEU arquivo; o sync nunca o reescreve
|
|
44
|
+
dd-harness/ tudo o que é gerado
|
|
45
|
+
politica.md
|
|
46
|
+
BRIEFING.md
|
|
47
|
+
brain/
|
|
48
|
+
manifest.json
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Na raiz fica apenas o **seu** `CLAUDE.md`, que importa a política com a linha
|
|
52
|
+
`@dd-harness/politica.md`. Isso deixa conviverem a parte gerenciada e o que o
|
|
53
|
+
repositório tem de próprio — adotar um projeto existente é acrescentar uma linha, não
|
|
54
|
+
sobrescrever nada.
|
|
55
|
+
|
|
56
|
+
O `sync` confere esse ponteiro a cada execução, inclusive quando o serviço não mudou:
|
|
57
|
+
import quebrado ou linha ausente faz a sessão abrir **sem política e sem avisar**. Isso
|
|
58
|
+
foi medido, não suposto.
|
|
59
|
+
|
|
60
|
+
## Deriva
|
|
61
|
+
|
|
62
|
+
`dd-harness check` mede as âncoras das memórias contra o estado real do repositório e
|
|
63
|
+
reporta o que saiu do lugar. Com `--commit <sha>` ele também cruza as âncoras com o diff
|
|
64
|
+
daquele commit — a memória volta ao code review. Nada bloqueia: avisa.
|
package/dist/argv.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leitura de argumentos da linha de comando.
|
|
3
|
+
*
|
|
4
|
+
* Vive fora do `index.ts` porque aquele modulo executa o CLI ao ser importado
|
|
5
|
+
* (`principal()` no topo) — teste que o importasse rodaria o programa inteiro.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Os termos livres de uma consulta, sem os pares `--flag valor`.
|
|
9
|
+
*
|
|
10
|
+
* Filtrar so o que comeca com `--` deixava o VALOR da flag para tras, e ele entrava na
|
|
11
|
+
* consulta: `buscar "efeito" --limite 2` virava a pergunta `"efeito 2"`. A busca roda, a
|
|
12
|
+
* resposta parece valida e responde a outra coisa — quem chamou nao tem como notar.
|
|
13
|
+
*/
|
|
14
|
+
export declare function termosDaConsulta(argv: string[]): string;
|
package/dist/argv.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leitura de argumentos da linha de comando.
|
|
3
|
+
*
|
|
4
|
+
* Vive fora do `index.ts` porque aquele modulo executa o CLI ao ser importado
|
|
5
|
+
* (`principal()` no topo) — teste que o importasse rodaria o programa inteiro.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Os termos livres de uma consulta, sem os pares `--flag valor`.
|
|
9
|
+
*
|
|
10
|
+
* Filtrar so o que comeca com `--` deixava o VALOR da flag para tras, e ele entrava na
|
|
11
|
+
* consulta: `buscar "efeito" --limite 2` virava a pergunta `"efeito 2"`. A busca roda, a
|
|
12
|
+
* resposta parece valida e responde a outra coisa — quem chamou nao tem como notar.
|
|
13
|
+
*/
|
|
14
|
+
export function termosDaConsulta(argv) {
|
|
15
|
+
const termos = [];
|
|
16
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
17
|
+
const atual = argv[i];
|
|
18
|
+
if (atual.startsWith("--")) {
|
|
19
|
+
// `--flag=valor` carrega o valor no proprio token; `--flag valor` consome o proximo.
|
|
20
|
+
if (!atual.includes("="))
|
|
21
|
+
i += 1;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
termos.push(atual);
|
|
25
|
+
}
|
|
26
|
+
return termos.join(" ").trim();
|
|
27
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ler e escrever a politica e o briefing do projeto.
|
|
3
|
+
*
|
|
4
|
+
* Ate aqui artefato so se editava pela interface, e isso deixava o agente sem saida para a
|
|
5
|
+
* unica coisa que ele mais precisa saber: a propria politica. O CLI exige que alguem ja
|
|
6
|
+
* tenha escrito nela que o CLI existe — e era justamente ela que estava fora de alcance.
|
|
7
|
+
*/
|
|
8
|
+
export type TipoDeArtefato = "politica" | "briefing";
|
|
9
|
+
/**
|
|
10
|
+
* O `GET` devolve o payload inteiro (politica, briefing, pastas e memorias); aqui so o
|
|
11
|
+
* artefato pedido interessa.
|
|
12
|
+
*
|
|
13
|
+
* `null` e `""` significam a mesma coisa para quem le — "ainda nao existe" —, e a diferenca
|
|
14
|
+
* entre elas e detalhe de como a linha foi parar no banco. Quem chama recebe `existe`, que
|
|
15
|
+
* e a pergunta real.
|
|
16
|
+
*/
|
|
17
|
+
export declare function leArtefato(raiz: string, tipo: TipoDeArtefato): Promise<{
|
|
18
|
+
conteudo: string;
|
|
19
|
+
existe: boolean;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Substituicao total, nao append: quem quer acrescentar um paragrafo le, concatena e
|
|
23
|
+
* reenvia. Conteudo vazio e valido e significa "apagar" — a linha fica no banco, guardando
|
|
24
|
+
* quem mexeu por ultimo.
|
|
25
|
+
*/
|
|
26
|
+
export declare function escreveArtefato(raiz: string, tipo: TipoDeArtefato, conteudo: string): Promise<{
|
|
27
|
+
artefato: string;
|
|
28
|
+
criou: boolean;
|
|
29
|
+
tamanho: number;
|
|
30
|
+
}>;
|
package/dist/artefato.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { cabecalhos, credencial, pede, recusa } from "./api.js";
|
|
2
|
+
/**
|
|
3
|
+
* O `GET` devolve o payload inteiro (politica, briefing, pastas e memorias); aqui so o
|
|
4
|
+
* artefato pedido interessa.
|
|
5
|
+
*
|
|
6
|
+
* `null` e `""` significam a mesma coisa para quem le — "ainda nao existe" —, e a diferenca
|
|
7
|
+
* entre elas e detalhe de como a linha foi parar no banco. Quem chama recebe `existe`, que
|
|
8
|
+
* e a pergunta real.
|
|
9
|
+
*/
|
|
10
|
+
export async function leArtefato(raiz, tipo) {
|
|
11
|
+
const { config, token } = await credencial(raiz);
|
|
12
|
+
const url = new URL(`${config.api}/api/v1/artefatos`);
|
|
13
|
+
url.searchParams.set("tenant", config.tenant);
|
|
14
|
+
url.searchParams.set("projeto", config.projeto);
|
|
15
|
+
const resposta = await pede(url, { headers: cabecalhos(token) });
|
|
16
|
+
if (!resposta.ok)
|
|
17
|
+
await recusa(resposta);
|
|
18
|
+
const payload = (await resposta.json());
|
|
19
|
+
const conteudo = typeof payload[tipo] === "string" ? payload[tipo] : "";
|
|
20
|
+
return { conteudo, existe: conteudo.length > 0 };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Substituicao total, nao append: quem quer acrescentar um paragrafo le, concatena e
|
|
24
|
+
* reenvia. Conteudo vazio e valido e significa "apagar" — a linha fica no banco, guardando
|
|
25
|
+
* quem mexeu por ultimo.
|
|
26
|
+
*/
|
|
27
|
+
export async function escreveArtefato(raiz, tipo, conteudo) {
|
|
28
|
+
const { config, token } = await credencial(raiz);
|
|
29
|
+
const resposta = await pede(`${config.api}/api/v1/artefatos`, {
|
|
30
|
+
method: "PUT",
|
|
31
|
+
headers: cabecalhos(token, true),
|
|
32
|
+
body: JSON.stringify({
|
|
33
|
+
tenant: config.tenant,
|
|
34
|
+
projeto: config.projeto,
|
|
35
|
+
tipo,
|
|
36
|
+
conteudo,
|
|
37
|
+
}),
|
|
38
|
+
});
|
|
39
|
+
if (!resposta.ok)
|
|
40
|
+
await recusa(resposta);
|
|
41
|
+
return (await resposta.json());
|
|
42
|
+
}
|
package/dist/check.d.ts
CHANGED
|
@@ -51,6 +51,11 @@ export type ResultadoDoStatus = {
|
|
|
51
51
|
memoria: string;
|
|
52
52
|
titulo: string;
|
|
53
53
|
}[];
|
|
54
|
+
/**
|
|
55
|
+
* Memorias que o indexador desistiu de processar. Sem embedding elas somem da busca
|
|
56
|
+
* semantica e continuam aparecendo no acervo — o unico sintoma e procurar e nao achar.
|
|
57
|
+
*/
|
|
58
|
+
travadasNaFila: number;
|
|
54
59
|
};
|
|
55
60
|
/** Separado do IO para poder ser testado sem rede: dado um Brain, o que se mede. */
|
|
56
61
|
export declare function medeAsAncoras(raiz: string, brain: Brain): Promise<Medicao[]>;
|
package/dist/check.js
CHANGED
|
@@ -72,6 +72,7 @@ export async function status(raiz) {
|
|
|
72
72
|
vencidas: ativas
|
|
73
73
|
.filter((m) => m.revisar_ate && new Date(m.revisar_ate).getTime() < agora)
|
|
74
74
|
.map((m) => ({ pasta: m.pasta, memoria: m.slug, titulo: m.titulo })),
|
|
75
|
+
travadasNaFila: brain.travadas_na_fila ?? 0,
|
|
75
76
|
};
|
|
76
77
|
}
|
|
77
78
|
export async function check(raiz, commit) {
|
package/dist/diff.d.ts
CHANGED
|
@@ -21,5 +21,10 @@ export type Tocada = {
|
|
|
21
21
|
/**
|
|
22
22
|
* Cruza caminhos com ancoras. A ancora casa quando e o proprio caminho ou quando e um
|
|
23
23
|
* diretorio que o contem — `supabase/migrations` tem que casar com a migration nova.
|
|
24
|
+
*
|
|
25
|
+
* Ancora de trecho (`arquivo.js#alvo`) casa pelo ARQUIVO: o `git diff-tree` devolve
|
|
26
|
+
* caminho puro, nunca com `#`, entao comparar o valor inteiro nao casava nunca — e em
|
|
27
|
+
* silencio, porque nao casar e resultado valido. O alvo continua no `Tocada.ancora`, que
|
|
28
|
+
* e o que a mensagem mostra: quem le precisa saber qual trecho a memoria guarda.
|
|
24
29
|
*/
|
|
25
30
|
export declare function memoriasTocadas(brain: Brain, caminhos: string[]): Tocada[];
|
package/dist/diff.js
CHANGED
|
@@ -30,6 +30,11 @@ export async function caminhosDoCommit(raiz, commit) {
|
|
|
30
30
|
/**
|
|
31
31
|
* Cruza caminhos com ancoras. A ancora casa quando e o proprio caminho ou quando e um
|
|
32
32
|
* diretorio que o contem — `supabase/migrations` tem que casar com a migration nova.
|
|
33
|
+
*
|
|
34
|
+
* Ancora de trecho (`arquivo.js#alvo`) casa pelo ARQUIVO: o `git diff-tree` devolve
|
|
35
|
+
* caminho puro, nunca com `#`, entao comparar o valor inteiro nao casava nunca — e em
|
|
36
|
+
* silencio, porque nao casar e resultado valido. O alvo continua no `Tocada.ancora`, que
|
|
37
|
+
* e o que a mensagem mostra: quem le precisa saber qual trecho a memoria guarda.
|
|
33
38
|
*/
|
|
34
39
|
export function memoriasTocadas(brain, caminhos) {
|
|
35
40
|
const tocadas = [];
|
|
@@ -37,7 +42,8 @@ export function memoriasTocadas(brain, caminhos) {
|
|
|
37
42
|
if (memoria.status !== "ativa")
|
|
38
43
|
continue;
|
|
39
44
|
for (const ancora of memoria.ancoras) {
|
|
40
|
-
const
|
|
45
|
+
const alvo = ancora.valor.split("#")[0];
|
|
46
|
+
const casa = caminhos.some((c) => c === alvo || c.startsWith(`${alvo}/`));
|
|
41
47
|
if (casa) {
|
|
42
48
|
tocadas.push({
|
|
43
49
|
pasta: memoria.pasta,
|
package/dist/gravar.js
CHANGED
|
@@ -46,9 +46,18 @@ export function interpreta(texto) {
|
|
|
46
46
|
throw new Error(`frontmatter sem \`${chave}\`.`);
|
|
47
47
|
}
|
|
48
48
|
// O corpo vai ate a secao dos filtros; dali para baixo e metadado, nao conteudo.
|
|
49
|
-
const
|
|
50
|
-
|
|
49
|
+
const cabecalhoDosFiltros = /\n##\s+Os tr[êe]s filtros\s*\n/gi;
|
|
50
|
+
const ocorrencias = [...resto.matchAll(cabecalhoDosFiltros)];
|
|
51
|
+
if (ocorrencias.length === 0)
|
|
51
52
|
throw new Error("falta a seção `## Os três filtros`.");
|
|
53
|
+
// Duas ocorrencias tornam o arquivo ambiguo: cortar na primeira trunca o corpo (e os
|
|
54
|
+
// filtros ainda saem certos da segunda, entao grava e perde dado sem avisar), e cortar
|
|
55
|
+
// na ultima escolheria em silencio qual secao e a de verdade. Recusar diz o que houve.
|
|
56
|
+
if (ocorrencias.length > 1) {
|
|
57
|
+
throw new Error("a seção `## Os três filtros` aparece mais de uma vez — deixe só a real, " +
|
|
58
|
+
"ou reescreva a citação no corpo (por exemplo, entre crases).");
|
|
59
|
+
}
|
|
60
|
+
const corte = ocorrencias[0].index;
|
|
52
61
|
const corpo = resto
|
|
53
62
|
.slice(0, corte)
|
|
54
63
|
.replace(/<!--[\s\S]*?-->/g, "")
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { termosDaConsulta } from "./argv.js";
|
|
2
3
|
import { check, status } from "./check.js";
|
|
3
4
|
import { leConfigDoRepo, guardaToken } from "./config.js";
|
|
4
5
|
import { grava } from "./gravar.js";
|
|
5
6
|
import { init, SUGESTAO_MCP } from "./init.js";
|
|
6
7
|
import { LINHA_DE_IMPORT } from "./materializa.js";
|
|
8
|
+
import { buscaPolitica } from "./politica.js";
|
|
7
9
|
import { busca } from "./buscar.js";
|
|
8
10
|
import { arquiva, edita } from "./curar.js";
|
|
9
11
|
import { criaPasta } from "./pasta.js";
|
|
@@ -17,29 +19,32 @@ import { sync } from "./sync.js";
|
|
|
17
19
|
* em repositorio alheio tem que envelhecer bem, e cada dependencia e uma chance de nao
|
|
18
20
|
* envelhecer.
|
|
19
21
|
*/
|
|
20
|
-
const AJUDA = `dd-harness — materializa política, briefing e Brain no repositório
|
|
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 sync escreve os artefatos em disco
|
|
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
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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}
|
|
43
48
|
`;
|
|
44
49
|
/** O aviso existe porque o import falha em silêncio — foi medido, não suposto. */
|
|
45
50
|
function avisaSobreOPonteiro(ponteiro) {
|
|
@@ -76,17 +81,17 @@ function avisaSobreOPonteiro(ponteiro) {
|
|
|
76
81
|
* nosso: escrever la dentro sem a pessoa pedir e o mesmo tipo de invasao que
|
|
77
82
|
* sobrescrever o CLAUDE.md dela. Quem cola, decide.
|
|
78
83
|
*/
|
|
79
|
-
const GANCHOS = `
|
|
80
|
-
Opcional — dois ganchos que valem a pena:
|
|
81
|
-
|
|
82
|
-
.git/hooks/post-commit (avisa quais memórias falam do que você mudou)
|
|
83
|
-
#!/bin/sh
|
|
84
|
-
dd-harness check --commit "$(git rev-parse HEAD)" || true
|
|
85
|
-
|
|
86
|
-
.claude/settings.json (na abertura da sessão, o que espera julgamento)
|
|
87
|
-
"hooks": { "SessionStart": [{ "hooks": [{ "type": "command",
|
|
88
|
-
"command": "dd-harness status" }] }] }
|
|
89
|
-
|
|
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
|
+
|
|
90
95
|
Os dois terminam em sucesso mesmo com deriva: avisam, não bloqueiam.`;
|
|
91
96
|
function argumento(argv, nome) {
|
|
92
97
|
const i = argv.indexOf(`--${nome}`);
|
|
@@ -164,7 +169,7 @@ async function comandoArquivar(argv) {
|
|
|
164
169
|
console.log(" foi para o histórico, não foi apagada. Rode `dd-harness sync`.");
|
|
165
170
|
}
|
|
166
171
|
async function comandoBuscar(argv) {
|
|
167
|
-
const consulta = argv
|
|
172
|
+
const consulta = termosDaConsulta(argv);
|
|
168
173
|
if (!consulta)
|
|
169
174
|
throw new Error('uso: dd-harness buscar "<pergunta>"');
|
|
170
175
|
const limite = Number(argumento(argv, "limite")) || undefined;
|
|
@@ -261,6 +266,27 @@ async function comandoCheck(argv) {
|
|
|
261
266
|
}
|
|
262
267
|
}
|
|
263
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Imprime a politica no stdout, para o hook `SessionStart` injetar no contexto.
|
|
271
|
+
*
|
|
272
|
+
* Os codigos de saida sao o contrato com o hook, e existem para separar duas coisas que
|
|
273
|
+
* um "falhou" generico colapsaria: projeto novo (que precisa de briefing) de politica
|
|
274
|
+
* inalcancavel (que precisa PARAR a sessao). Mudar estes numeros quebra o hook.
|
|
275
|
+
*/
|
|
276
|
+
async function comandoPolitica() {
|
|
277
|
+
const r = await buscaPolitica(process.cwd());
|
|
278
|
+
if (r.estado === "ok") {
|
|
279
|
+
console.log(r.conteudo);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (r.estado === "sem-politica") {
|
|
283
|
+
console.error("Este projeto ainda não tem política — nunca foi briefado. Rode `/briefar`.");
|
|
284
|
+
process.exitCode = 3;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
console.error(`não consegui buscar a política: ${r.motivo}`);
|
|
288
|
+
process.exitCode = 1;
|
|
289
|
+
}
|
|
264
290
|
async function comandoStatus() {
|
|
265
291
|
const r = await status(process.cwd());
|
|
266
292
|
// O acervo vem primeiro, e sempre: e a resposta para "o que ha no Brain deste projeto?",
|
|
@@ -276,6 +302,14 @@ async function comandoStatus() {
|
|
|
276
302
|
(arquivadas ? ` e ${arquivadas} arquivada(s)` : "") +
|
|
277
303
|
(detalhe ? ` — ${detalhe}` : ""));
|
|
278
304
|
}
|
|
305
|
+
// Antes do early return abaixo: fila travada nao e "julgamento esperando", e sumiria
|
|
306
|
+
// justamente na sessao mais comum — a que nao tem deriva nem vencida.
|
|
307
|
+
if (r.travadasNaFila > 0) {
|
|
308
|
+
console.log("");
|
|
309
|
+
console.log(`AVISO: ${r.travadasNaFila} memória(s) desistiram de ser indexadas e não entram na busca semântica.`);
|
|
310
|
+
console.log(" O indexador tentou 5 vezes e parou. Rode `pnpm worker --reindexar`;");
|
|
311
|
+
console.log(" se repetir, o motivo está em `embedding_queue.ultimo_erro`.");
|
|
312
|
+
}
|
|
279
313
|
if (!r.comDeriva.length && !r.vencidas.length) {
|
|
280
314
|
console.log("Nada esperando julgamento.");
|
|
281
315
|
return;
|
|
@@ -366,6 +400,8 @@ async function principal() {
|
|
|
366
400
|
return comandoCheck(resto);
|
|
367
401
|
case "status":
|
|
368
402
|
return comandoStatus();
|
|
403
|
+
case "politica":
|
|
404
|
+
return comandoPolitica();
|
|
369
405
|
case "--help":
|
|
370
406
|
case "-h":
|
|
371
407
|
case undefined:
|
package/dist/init.js
CHANGED
|
@@ -10,12 +10,12 @@ import { LINHA_DE_IMPORT } from "./materializa.js";
|
|
|
10
10
|
* acrescenta a linha ao que voce escreveu, sem tocar no resto. Adotar um projeto vira
|
|
11
11
|
* uma linha, e nao um ritual de mover arquivo.
|
|
12
12
|
*/
|
|
13
|
-
const CABECALHO = `# CLAUDE.md
|
|
14
|
-
|
|
15
|
-
Este arquivo é seu: escreva aqui o que for específico deste repositório.
|
|
16
|
-
|
|
17
|
-
A linha abaixo importa a política gerenciada pelo dd-harness. **Não a remova** — sem
|
|
18
|
-
ela a sessão abre sem protocolo, e nada avisa.
|
|
13
|
+
const CABECALHO = `# CLAUDE.md
|
|
14
|
+
|
|
15
|
+
Este arquivo é seu: escreva aqui o que for específico deste repositório.
|
|
16
|
+
|
|
17
|
+
A linha abaixo importa a política gerenciada pelo dd-harness. **Não a remova** — sem
|
|
18
|
+
ela a sessão abre sem protocolo, e nada avisa.
|
|
19
19
|
`;
|
|
20
20
|
/**
|
|
21
21
|
* O `.mcp.json` e sugerido, nunca escrito.
|
|
@@ -24,13 +24,13 @@ ela a sessão abre sem protocolo, e nada avisa.
|
|
|
24
24
|
* outros servidores, e mesclar JSON alheio e onde falha silenciosa nasce — entrada errada
|
|
25
25
|
* nao da erro, a ferramenta so nao aparece. Quem cola sabe o que colou.
|
|
26
26
|
*/
|
|
27
|
-
export const SUGESTAO_MCP = `{
|
|
28
|
-
"mcpServers": {
|
|
29
|
-
"dd-harness": {
|
|
30
|
-
"command": "npx",
|
|
31
|
-
"args": ["-y", "dd-harness-mcp"]
|
|
32
|
-
}
|
|
33
|
-
}
|
|
27
|
+
export const SUGESTAO_MCP = `{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"dd-harness": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["-y", "dd-harness-mcp"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
34
|
}`;
|
|
35
35
|
async function declaraMcp(raiz) {
|
|
36
36
|
try {
|
package/dist/materializa.d.ts
CHANGED
|
@@ -44,6 +44,11 @@ export type Brain = {
|
|
|
44
44
|
definicao: string;
|
|
45
45
|
}[];
|
|
46
46
|
memorias: Memoria[];
|
|
47
|
+
/**
|
|
48
|
+
* Memorias que excederam as tentativas de indexacao e nao serao mais tentadas. Ficam
|
|
49
|
+
* sem embedding — somem da busca semantica — e so este numero denuncia.
|
|
50
|
+
*/
|
|
51
|
+
travadas_na_fila?: number;
|
|
47
52
|
};
|
|
48
53
|
export declare function arquivoDaMemoria(m: Memoria): string;
|
|
49
54
|
export declare function arquivoDoIndice(brain: Brain): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A politica do projeto, buscada no servico na hora.
|
|
3
|
+
*
|
|
4
|
+
* Existe para o hook `SessionStart`: sem materializacao em disco, e ele quem garante que
|
|
5
|
+
* nenhuma sessao abre sem protocolo. Por isso o retorno separa TRES situacoes que um
|
|
6
|
+
* "deu erro" colapsaria — e cada uma pede uma reacao diferente de quem chamou:
|
|
7
|
+
*
|
|
8
|
+
* - `ok`: a politica existe e veio. Segue a sessao.
|
|
9
|
+
* - `sem-politica`: o projeto existe e nunca foi briefado. NAO e falha: a saida e rodar o
|
|
10
|
+
* briefing. Tratar como erro faria todo projeto novo parecer quebrado.
|
|
11
|
+
* - `inalcancavel`: rede, timeout, 5xx, credencial, projeto inexistente. A politica pode
|
|
12
|
+
* existir e nao chegou — e ai a sessao precisa PARAR, porque seguir sem ela e
|
|
13
|
+
* exatamente a falha que este projeto combate.
|
|
14
|
+
*
|
|
15
|
+
* A diferenca entre as duas ultimas e o que o `GET /api/v1/artefatos` responde: `404` e
|
|
16
|
+
* "nao ha projeto"; `200` com `politica` nula e "existe, nunca briefado".
|
|
17
|
+
*/
|
|
18
|
+
export type ResultadoDaPolitica = {
|
|
19
|
+
estado: "ok";
|
|
20
|
+
conteudo: string;
|
|
21
|
+
} | {
|
|
22
|
+
estado: "sem-politica";
|
|
23
|
+
} | {
|
|
24
|
+
estado: "inalcancavel";
|
|
25
|
+
motivo: string;
|
|
26
|
+
};
|
|
27
|
+
export declare function buscaPolitica(raiz: string): Promise<ResultadoDaPolitica>;
|
package/dist/politica.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { cabecalhos, credencial, pede } from "./api.js";
|
|
2
|
+
export async function buscaPolitica(raiz) {
|
|
3
|
+
let config;
|
|
4
|
+
try {
|
|
5
|
+
config = await credencial(raiz);
|
|
6
|
+
}
|
|
7
|
+
catch (erro) {
|
|
8
|
+
// Sem `.dd-harness.json` ou sem token nao da para nem perguntar. E inalcancavel, nao
|
|
9
|
+
// "sem politica": a politica pode muito bem existir do outro lado.
|
|
10
|
+
return { estado: "inalcancavel", motivo: mensagem(erro) };
|
|
11
|
+
}
|
|
12
|
+
const url = new URL(`${config.config.api}/api/v1/artefatos`);
|
|
13
|
+
url.searchParams.set("tenant", config.config.tenant);
|
|
14
|
+
url.searchParams.set("projeto", config.config.projeto);
|
|
15
|
+
try {
|
|
16
|
+
const resposta = await pede(url, { headers: cabecalhos(config.token) });
|
|
17
|
+
if (!resposta.ok) {
|
|
18
|
+
const { erro } = (await resposta.json().catch(() => ({})));
|
|
19
|
+
return {
|
|
20
|
+
estado: "inalcancavel",
|
|
21
|
+
motivo: erro ?? `a API respondeu ${resposta.status}.`,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const payload = (await resposta.json());
|
|
25
|
+
const conteudo = payload.politica?.trim();
|
|
26
|
+
// Vazio e nulo sao a mesma coisa aqui, e os dois significam "nunca foi escrita".
|
|
27
|
+
if (!conteudo)
|
|
28
|
+
return { estado: "sem-politica" };
|
|
29
|
+
return { estado: "ok", conteudo };
|
|
30
|
+
}
|
|
31
|
+
catch (erro) {
|
|
32
|
+
return { estado: "inalcancavel", motivo: mensagem(erro) };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const mensagem = (erro) => erro instanceof Error ? erro.message : String(erro);
|
package/package.json
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dd-harness",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "Materializa politica, briefing e Brain do dd-harness no repositorio. 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.3.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Materializa politica, briefing e Brain do dd-harness no repositorio. 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
|
+
}
|