dd-harness 0.4.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/brain.d.ts +5 -0
- package/dist/buscar.d.ts +11 -4
- package/dist/buscar.js +6 -1
- package/dist/check.d.ts +6 -1
- package/dist/check.js +21 -5
- package/dist/diff.d.ts +59 -2
- package/dist/diff.js +74 -16
- package/dist/index.js +105 -10
- package/dist/init.d.ts +15 -0
- package/dist/init.js +43 -0
- package/dist/politica.d.ts +7 -1
- package/dist/politica.js +3 -2
- package/dist/reancorar.d.ts +16 -0
- package/dist/reancorar.js +48 -0
- package/dist/worker.d.ts +37 -0
- package/dist/worker.js +65 -0
- package/package.json +1 -1
package/dist/brain.d.ts
CHANGED
|
@@ -47,4 +47,9 @@ export type Brain = {
|
|
|
47
47
|
* sem embedding — somem da busca semantica — e so este numero denuncia.
|
|
48
48
|
*/
|
|
49
49
|
travadas_na_fila?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Memorias que so esperam o worker rodar. Voltam sozinhas assim que alguem o sobe —
|
|
52
|
+
* por isso o cliente age em cima deste numero, em vez de so avisar.
|
|
53
|
+
*/
|
|
54
|
+
esperando_indexacao?: number;
|
|
50
55
|
};
|
package/dist/buscar.d.ts
CHANGED
|
@@ -5,16 +5,23 @@
|
|
|
5
5
|
* esta responde "o que ja aprendemos sobre isto?", que e a pergunta de quem ainda esta
|
|
6
6
|
* entendendo o problema e nem sabe qual arquivo abrir.
|
|
7
7
|
*
|
|
8
|
-
* Devolve endereco, nao conteudo: quem quiser o corpo
|
|
9
|
-
*
|
|
8
|
+
* Devolve endereco, nao conteudo: quem quiser o corpo chama `ler`. Assim a busca continua
|
|
9
|
+
* barata, e quem so queria saber "existe algo sobre isto?" nao paga por N corpos.
|
|
10
10
|
*/
|
|
11
11
|
export type Achado = {
|
|
12
12
|
endereco: string;
|
|
13
13
|
titulo: string;
|
|
14
14
|
resumo: string;
|
|
15
15
|
};
|
|
16
|
-
export
|
|
16
|
+
export type Resultado = {
|
|
17
17
|
semantica: boolean;
|
|
18
18
|
provedor: string | null;
|
|
19
19
|
achados: Achado[];
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Memorias sem vetor no momento da busca. Maior que zero significa que PODE existir
|
|
22
|
+
* resposta melhor que nao apareceu — e quem chamou nao teria como saber, porque o
|
|
23
|
+
* resultado parece completo de qualquer forma.
|
|
24
|
+
*/
|
|
25
|
+
esperandoIndexacao: number;
|
|
26
|
+
};
|
|
27
|
+
export declare function busca(raiz: string, consulta: string, limite?: number): Promise<Resultado>;
|
package/dist/buscar.js
CHANGED
|
@@ -11,5 +11,10 @@ export async function busca(raiz, consulta, limite) {
|
|
|
11
11
|
if (!resposta.ok)
|
|
12
12
|
await recusa(resposta);
|
|
13
13
|
const lido = (await resposta.json());
|
|
14
|
-
return {
|
|
14
|
+
return {
|
|
15
|
+
semantica: lido.semantica,
|
|
16
|
+
provedor: lido.provedor,
|
|
17
|
+
achados: lido.resultados,
|
|
18
|
+
esperandoIndexacao: lido.esperando_indexacao ?? 0,
|
|
19
|
+
};
|
|
15
20
|
}
|
package/dist/check.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Tocada } from "./diff.js";
|
|
1
|
+
import { type Reancoragem, type Tocada } from "./diff.js";
|
|
2
2
|
import type { Brain } from "./brain.js";
|
|
3
3
|
/**
|
|
4
4
|
* `dd-harness check` — resolve as âncoras contra a árvore de trabalho.
|
|
@@ -24,6 +24,11 @@ export type ResultadoDoCheck = {
|
|
|
24
24
|
fechadas: number;
|
|
25
25
|
/** Memorias que falam do que o commit mudou. Vazio quando nao ha `--commit`. */
|
|
26
26
|
tocadas: Tocada[];
|
|
27
|
+
/**
|
|
28
|
+
* Para onde o git sugere que cada alvo ausente foi. So com `--commit`: sem diff nao ha
|
|
29
|
+
* rename a cruzar, e a lista de ausentes continua sendo tudo o que se pode dizer.
|
|
30
|
+
*/
|
|
31
|
+
reancoragens: Reancoragem[];
|
|
27
32
|
};
|
|
28
33
|
export type ResultadoDoStatus = {
|
|
29
34
|
/**
|
package/dist/check.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { pede } from "./api.js";
|
|
2
2
|
import { leConfigDoRepo, leToken } from "./config.js";
|
|
3
|
-
import { caminhosDoCommit, memoriasTocadas } from "./diff.js";
|
|
3
|
+
import { caminhosDoCommit, memoriasTocadas, sugereReancoragem, } from "./diff.js";
|
|
4
4
|
import { mede } from "./medir.js";
|
|
5
5
|
/** Separado do IO para poder ser testado sem rede: dado um Brain, o que se mede. */
|
|
6
6
|
export async function medeAsAncoras(raiz, brain) {
|
|
@@ -80,11 +80,26 @@ export async function check(raiz, commit) {
|
|
|
80
80
|
const cabecalhos = { Authorization: `Bearer ${token}` };
|
|
81
81
|
const medicoes = await medeAsAncoras(raiz, brain);
|
|
82
82
|
// O cruzamento com o diff so faz sentido quando ha um commit para olhar.
|
|
83
|
-
const
|
|
84
|
-
?
|
|
85
|
-
: [];
|
|
83
|
+
const mudancas = commit
|
|
84
|
+
? await caminhosDoCommit(raiz, commit)
|
|
85
|
+
: { caminhos: [], renames: [] };
|
|
86
|
+
const tocadas = commit ? memoriasTocadas(brain, mudancas.caminhos) : [];
|
|
87
|
+
// Para onde o alvo ausente foi. Sem `--commit` a lista de renames e vazia, entao isto
|
|
88
|
+
// devolve vazio sozinho — nao ha caso especial a escrever.
|
|
89
|
+
const reancoragens = sugereReancoragem(medicoes
|
|
90
|
+
.filter((m) => m.sha === null)
|
|
91
|
+
.map((m) => ({ pasta: m.pasta, memoria: m.memoria, valor: m.valor })), mudancas.renames);
|
|
86
92
|
if (medicoes.length === 0) {
|
|
87
|
-
return {
|
|
93
|
+
return {
|
|
94
|
+
medidas: 0,
|
|
95
|
+
ausentes: [],
|
|
96
|
+
novas: 0,
|
|
97
|
+
base: 0,
|
|
98
|
+
jaAbertas: 0,
|
|
99
|
+
fechadas: 0,
|
|
100
|
+
tocadas,
|
|
101
|
+
reancoragens,
|
|
102
|
+
};
|
|
88
103
|
}
|
|
89
104
|
const envio = await pede(`${config.api}/api/v1/deriva`, {
|
|
90
105
|
method: "POST",
|
|
@@ -111,5 +126,6 @@ export async function check(raiz, commit) {
|
|
|
111
126
|
jaAbertas: julgamento.ja_abertas,
|
|
112
127
|
fechadas: julgamento.fechadas ?? 0,
|
|
113
128
|
tocadas,
|
|
129
|
+
reancoragens,
|
|
114
130
|
};
|
|
115
131
|
}
|
package/dist/diff.d.ts
CHANGED
|
@@ -10,14 +10,71 @@ import type { Brain } from "./brain.js";
|
|
|
10
10
|
* "voce acabou de mexer no que esta memoria guarda" — util mesmo quando a base ainda
|
|
11
11
|
* nem foi medida, e util mesmo que a memoria continue valendo.
|
|
12
12
|
*/
|
|
13
|
-
/**
|
|
14
|
-
export
|
|
13
|
+
/** Um arquivo que saiu de um caminho e chegou noutro, com a confianca que o git deu. */
|
|
14
|
+
export type Rename = {
|
|
15
|
+
de: string;
|
|
16
|
+
para: string;
|
|
17
|
+
similaridade: number;
|
|
18
|
+
};
|
|
19
|
+
export type MudancasDoCommit = {
|
|
20
|
+
caminhos: string[];
|
|
21
|
+
renames: Rename[];
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* O que um commit mudou: caminhos tocados e renames detectados.
|
|
25
|
+
*
|
|
26
|
+
* `-M` liga a deteccao de rename por similaridade, e e o que torna a reancoragem
|
|
27
|
+
* possivel: sem ela, mover um arquivo aparece como um apagado mais um criado, e a ancora
|
|
28
|
+
* que apontava para o antigo so tem "alvo ausente" a dizer. Com ela, o git responde para
|
|
29
|
+
* ONDE o conteudo foi, com um percentual de confianca.
|
|
30
|
+
*
|
|
31
|
+
* Vazio quando o git nao responde — nunca quebra: um aviso que nao pode ser dado nao vira
|
|
32
|
+
* erro que atrapalha o commit.
|
|
33
|
+
*/
|
|
34
|
+
export declare function caminhosDoCommit(raiz: string, commit: string): Promise<MudancasDoCommit>;
|
|
35
|
+
/**
|
|
36
|
+
* Le a saida de `--name-status`: uma letra de status, TAB, e um ou dois caminhos.
|
|
37
|
+
*
|
|
38
|
+
* Rename vem como `R<similaridade>\t<de>\t<para>` (ex: `R094`), e conta como mudanca nos
|
|
39
|
+
* DOIS caminhos: quem tinha ancora no antigo precisa saber, e quem tem ancora no novo
|
|
40
|
+
* tambem — o conteudo chegou la.
|
|
41
|
+
*/
|
|
42
|
+
export declare function interpretaNameStatus(stdout: string): MudancasDoCommit;
|
|
15
43
|
export type Tocada = {
|
|
16
44
|
pasta: string;
|
|
17
45
|
memoria: string;
|
|
18
46
|
titulo: string;
|
|
19
47
|
ancora: string;
|
|
20
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* Uma ancora cujo alvo sumiu, e para onde o git sugere que ele foi.
|
|
51
|
+
*
|
|
52
|
+
* A sugestao e heuristica: o git compara conteudo e da um percentual. Por isso ela e
|
|
53
|
+
* SUGERIDA e nunca aplicada sozinha — reancorar em silencio moveria a memoria para o
|
|
54
|
+
* lugar errado sem ninguem ver, que e pior que o alvo ausente que se queria resolver.
|
|
55
|
+
*/
|
|
56
|
+
export type Reancoragem = {
|
|
57
|
+
pasta: string;
|
|
58
|
+
memoria: string;
|
|
59
|
+
ancora: string;
|
|
60
|
+
sugestao: string;
|
|
61
|
+
similaridade: number;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Cruza ancoras ausentes com os renames do commit.
|
|
65
|
+
*
|
|
66
|
+
* O caso que isto resolve: uma refatoracao move um arquivo (ou uma pasta inteira) e toda
|
|
67
|
+
* memoria ancorada ali passa a acusar "alvo ausente" de uma vez. A lista sozinha nao
|
|
68
|
+
* ajuda — o git sabe para onde o conteudo foi, e e essa a resposta que faltava.
|
|
69
|
+
*
|
|
70
|
+
* Ancora de trecho (`arquivo#alvo`) mantem o alvo na sugestao: o arquivo mudou de lugar,
|
|
71
|
+
* o trecho dentro dele provavelmente nao.
|
|
72
|
+
*/
|
|
73
|
+
export declare function sugereReancoragem(ausentes: {
|
|
74
|
+
pasta: string;
|
|
75
|
+
memoria: string;
|
|
76
|
+
valor: string;
|
|
77
|
+
}[], renames: Rename[]): Reancoragem[];
|
|
21
78
|
/**
|
|
22
79
|
* Cruza caminhos com ancoras. A ancora casa quando e o proprio caminho ou quando e um
|
|
23
80
|
* diretorio que o contem — `supabase/migrations` tem que casar com a migration nova.
|
package/dist/diff.js
CHANGED
|
@@ -2,31 +2,89 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
const roda = promisify(execFile);
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* O que um commit mudou: caminhos tocados e renames detectados.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* `-M` liga a deteccao de rename por similaridade, e e o que torna a reancoragem
|
|
8
|
+
* possivel: sem ela, mover um arquivo aparece como um apagado mais um criado, e a ancora
|
|
9
|
+
* que apontava para o antigo so tem "alvo ausente" a dizer. Com ela, o git responde para
|
|
10
|
+
* ONDE o conteudo foi, com um percentual de confianca.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* nem foi medida, e util mesmo que a memoria continue valendo.
|
|
12
|
+
* Vazio quando o git nao responde — nunca quebra: um aviso que nao pode ser dado nao vira
|
|
13
|
+
* erro que atrapalha o commit.
|
|
14
14
|
*/
|
|
15
|
-
/** Caminhos alterados num commit. Vazio quando o git nao responde — nunca quebra. */
|
|
16
15
|
export async function caminhosDoCommit(raiz, commit) {
|
|
17
16
|
try {
|
|
18
|
-
const { stdout } = await roda("git", ["diff-tree", "--no-commit-id", "
|
|
19
|
-
return stdout
|
|
20
|
-
.split(/\r?\n/)
|
|
21
|
-
.map((l) => l.trim())
|
|
22
|
-
.filter(Boolean);
|
|
17
|
+
const { stdout } = await roda("git", ["diff-tree", "--no-commit-id", "-r", "-M", "--name-status", commit], { cwd: raiz });
|
|
18
|
+
return interpretaNameStatus(stdout);
|
|
23
19
|
}
|
|
24
20
|
catch {
|
|
25
|
-
|
|
26
|
-
// acontece. Um aviso que nao pode ser dado nao vira erro que atrapalha o commit.
|
|
27
|
-
return [];
|
|
21
|
+
return { caminhos: [], renames: [] };
|
|
28
22
|
}
|
|
29
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Le a saida de `--name-status`: uma letra de status, TAB, e um ou dois caminhos.
|
|
26
|
+
*
|
|
27
|
+
* Rename vem como `R<similaridade>\t<de>\t<para>` (ex: `R094`), e conta como mudanca nos
|
|
28
|
+
* DOIS caminhos: quem tinha ancora no antigo precisa saber, e quem tem ancora no novo
|
|
29
|
+
* tambem — o conteudo chegou la.
|
|
30
|
+
*/
|
|
31
|
+
export function interpretaNameStatus(stdout) {
|
|
32
|
+
const caminhos = [];
|
|
33
|
+
const renames = [];
|
|
34
|
+
for (const linha of stdout.split(/\r?\n/)) {
|
|
35
|
+
if (!linha.trim())
|
|
36
|
+
continue;
|
|
37
|
+
const [status, primeiro, segundo] = linha.split("\t");
|
|
38
|
+
if (!status || !primeiro)
|
|
39
|
+
continue;
|
|
40
|
+
if (status.startsWith("R") && segundo) {
|
|
41
|
+
caminhos.push(primeiro, segundo);
|
|
42
|
+
renames.push({
|
|
43
|
+
de: primeiro,
|
|
44
|
+
para: segundo,
|
|
45
|
+
// `R094` -> 94. Sem numero (formatos antigos do git), 0 diz "nao sei o quanto".
|
|
46
|
+
similaridade: Number(status.slice(1)) || 0,
|
|
47
|
+
});
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
caminhos.push(primeiro);
|
|
51
|
+
}
|
|
52
|
+
return { caminhos, renames };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Cruza ancoras ausentes com os renames do commit.
|
|
56
|
+
*
|
|
57
|
+
* O caso que isto resolve: uma refatoracao move um arquivo (ou uma pasta inteira) e toda
|
|
58
|
+
* memoria ancorada ali passa a acusar "alvo ausente" de uma vez. A lista sozinha nao
|
|
59
|
+
* ajuda — o git sabe para onde o conteudo foi, e e essa a resposta que faltava.
|
|
60
|
+
*
|
|
61
|
+
* Ancora de trecho (`arquivo#alvo`) mantem o alvo na sugestao: o arquivo mudou de lugar,
|
|
62
|
+
* o trecho dentro dele provavelmente nao.
|
|
63
|
+
*/
|
|
64
|
+
export function sugereReancoragem(ausentes, renames) {
|
|
65
|
+
const sugestoes = [];
|
|
66
|
+
for (const ausente of ausentes) {
|
|
67
|
+
const corte = ausente.valor.indexOf("#");
|
|
68
|
+
const arquivo = corte === -1 ? ausente.valor : ausente.valor.slice(0, corte);
|
|
69
|
+
const trecho = corte === -1 ? "" : ausente.valor.slice(corte);
|
|
70
|
+
// Casa o arquivo exato ou um diretorio que o continha: mover `src/db/` inteiro
|
|
71
|
+
// renomeia cada arquivo dentro, e a ancora de diretorio precisa achar isso.
|
|
72
|
+
const rename = renames.find((r) => r.de === arquivo || arquivo.startsWith(`${r.de}/`));
|
|
73
|
+
if (!rename)
|
|
74
|
+
continue;
|
|
75
|
+
const destino = rename.de === arquivo
|
|
76
|
+
? rename.para
|
|
77
|
+
: `${rename.para}${arquivo.slice(rename.de.length)}`;
|
|
78
|
+
sugestoes.push({
|
|
79
|
+
pasta: ausente.pasta,
|
|
80
|
+
memoria: ausente.memoria,
|
|
81
|
+
ancora: ausente.valor,
|
|
82
|
+
sugestao: `${destino}${trecho}`,
|
|
83
|
+
similaridade: rename.similaridade,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return sugestoes;
|
|
87
|
+
}
|
|
30
88
|
/**
|
|
31
89
|
* Cruza caminhos com ancoras. A ancora casa quando e o proprio caminho ou quando e um
|
|
32
90
|
* diretorio que o contem — `supabase/migrations` tem que casar com a migration nova.
|
package/dist/index.js
CHANGED
|
@@ -3,12 +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_HOOK, SUGESTAO_MCP } from "./init.js";
|
|
6
|
+
import { init, SUGESTAO_AGENTS, SUGESTAO_HOOK, SUGESTAO_MCP } from "./init.js";
|
|
7
7
|
import { buscaPolitica } from "./politica.js";
|
|
8
8
|
import { busca } from "./buscar.js";
|
|
9
9
|
import { arquiva, edita, le } from "./curar.js";
|
|
10
10
|
import { criaPasta } from "./pasta.js";
|
|
11
11
|
import { criaProjeto } from "./projeto.js";
|
|
12
|
+
import { reancora } from "./reancorar.js";
|
|
13
|
+
import { subiuOWorker } from "./worker.js";
|
|
12
14
|
/**
|
|
13
15
|
* `dd-harness` — o cliente que materializa os artefatos no repositorio.
|
|
14
16
|
*
|
|
@@ -34,6 +36,8 @@ const AJUDA = `dd-harness — a política e o Brain do projeto, no serviço
|
|
|
34
36
|
tira de circulação sem apagar
|
|
35
37
|
dd-harness buscar "<pergunta>" acha memória por relevância, não por arquivo
|
|
36
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
|
|
37
41
|
dd-harness check [--commit <sha>] mede as âncoras e reporta a deriva
|
|
38
42
|
dd-harness status só lê: o tamanho do Brain e o que espera julgamento
|
|
39
43
|
dd-harness politica [--hook] imprime a política do serviço
|
|
@@ -93,12 +97,24 @@ async function comandoInit(argv) {
|
|
|
93
97
|
}
|
|
94
98
|
if (r.mcp === "ja-declarado") {
|
|
95
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");
|
|
96
112
|
return;
|
|
97
113
|
}
|
|
98
|
-
console.log("\
|
|
99
|
-
"\
|
|
100
|
-
"\
|
|
101
|
-
console.log(
|
|
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);
|
|
102
118
|
}
|
|
103
119
|
/**
|
|
104
120
|
* Nao exige `.dd-harness.json`, e isso importa: sem projeto no servico o `init` nao tem o
|
|
@@ -170,6 +186,7 @@ async function comandoBuscar(argv) {
|
|
|
170
186
|
if (!r.semantica) {
|
|
171
187
|
console.log(" (só busca textual: sem provedor de embedding no serviço)");
|
|
172
188
|
}
|
|
189
|
+
avisaSobreAFila(r.esperandoIndexacao);
|
|
173
190
|
return;
|
|
174
191
|
}
|
|
175
192
|
console.log(r.semantica
|
|
@@ -179,6 +196,32 @@ async function comandoBuscar(argv) {
|
|
|
179
196
|
console.log(` ${a.endereco} — ${a.titulo}`);
|
|
180
197
|
console.log(` ${a.resumo}`);
|
|
181
198
|
}
|
|
199
|
+
avisaSobreAFila(r.esperandoIndexacao);
|
|
200
|
+
}
|
|
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
|
+
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>"');
|
|
219
|
+
}
|
|
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.");
|
|
182
225
|
}
|
|
183
226
|
async function comandoCheck(argv) {
|
|
184
227
|
const r = await check(process.cwd(), argumento(argv, "commit"));
|
|
@@ -195,8 +238,24 @@ async function comandoCheck(argv) {
|
|
|
195
238
|
if (r.ausentes.length) {
|
|
196
239
|
console.log("");
|
|
197
240
|
console.log("ALVO AUSENTE — alguém apagou ou moveu o que uma memória guarda:");
|
|
198
|
-
for (const valor of r.ausentes)
|
|
241
|
+
for (const valor of r.ausentes) {
|
|
199
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
|
+
}
|
|
200
259
|
}
|
|
201
260
|
if (r.novas > 0) {
|
|
202
261
|
console.log("");
|
|
@@ -239,7 +298,11 @@ async function comandoPolitica(argv) {
|
|
|
239
298
|
// no terminal. A diferenca importa: o hook precisa que o AVISO chegue ao modelo, e
|
|
240
299
|
// stderr so chega ao transcript — aviso que o modelo nao le e o mesmo que silencio.
|
|
241
300
|
if (argv.includes("--hook")) {
|
|
242
|
-
|
|
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) }));
|
|
243
306
|
return;
|
|
244
307
|
}
|
|
245
308
|
if (r.estado === "ok") {
|
|
@@ -261,14 +324,16 @@ async function comandoPolitica(argv) {
|
|
|
261
324
|
* so faria o Claude Code registrar falha no transcript — que ninguem le — enquanto a
|
|
262
325
|
* sessao seguiria sem saber que esta sem protocolo.
|
|
263
326
|
*/
|
|
264
|
-
function contextoDaSessao(r) {
|
|
327
|
+
function contextoDaSessao(r, worker) {
|
|
265
328
|
const base = { hookEventName: "SessionStart" };
|
|
329
|
+
const fila = avisoDaFila(r.esperandoIndexacao ?? 0, worker);
|
|
266
330
|
if (r.estado === "ok") {
|
|
267
331
|
return {
|
|
268
332
|
...base,
|
|
269
333
|
additionalContext: "# Política deste projeto (carregada do dd-harness)\n\n" +
|
|
270
334
|
"As regras abaixo valem para esta sessão inteira.\n\n" +
|
|
271
|
-
r.conteudo
|
|
335
|
+
r.conteudo +
|
|
336
|
+
fila,
|
|
272
337
|
};
|
|
273
338
|
}
|
|
274
339
|
if (r.estado === "sem-politica") {
|
|
@@ -276,7 +341,8 @@ function contextoDaSessao(r) {
|
|
|
276
341
|
...base,
|
|
277
342
|
additionalContext: "AVISO DO DD-HARNESS: este projeto existe no serviço mas **nunca foi briefado** " +
|
|
278
343
|
"— 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`."
|
|
344
|
+
"implementar qualquer coisa, diga isso ao usuário e proponha rodar `/briefar`." +
|
|
345
|
+
fila,
|
|
280
346
|
};
|
|
281
347
|
}
|
|
282
348
|
return {
|
|
@@ -290,6 +356,33 @@ function contextoDaSessao(r) {
|
|
|
290
356
|
"tivesse acontecido é exatamente a falha que este projeto combate.",
|
|
291
357
|
};
|
|
292
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
|
+
}
|
|
293
386
|
async function comandoStatus() {
|
|
294
387
|
const r = await status(process.cwd());
|
|
295
388
|
// O acervo vem primeiro, e sempre: e a resposta para "o que ha no Brain deste projeto?",
|
|
@@ -397,6 +490,8 @@ async function principal() {
|
|
|
397
490
|
return comandoLer(resto);
|
|
398
491
|
case "buscar":
|
|
399
492
|
return comandoBuscar(resto);
|
|
493
|
+
case "reancorar":
|
|
494
|
+
return comandoReancorar(resto);
|
|
400
495
|
case "check":
|
|
401
496
|
return comandoCheck(resto);
|
|
402
497
|
case "status":
|
package/dist/init.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export type ResultadoDoInit = {
|
|
|
12
12
|
config: "criada" | "ja-existia";
|
|
13
13
|
mcp: "ja-declarado" | "a-declarar";
|
|
14
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";
|
|
15
17
|
};
|
|
16
18
|
/**
|
|
17
19
|
* O `.mcp.json` e sugerido, nunca escrito.
|
|
@@ -32,6 +34,19 @@ export declare const SUGESTAO_MCP = "{\n \"mcpServers\": {\n \"dd-harness\":
|
|
|
32
34
|
* Sugerido e nao escrito, pelo mesmo motivo do `.mcp.json`.
|
|
33
35
|
*/
|
|
34
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.";
|
|
35
50
|
export declare function init(raiz: string, dados: {
|
|
36
51
|
tenant: string;
|
|
37
52
|
projeto: string;
|
package/dist/init.js
CHANGED
|
@@ -41,6 +41,32 @@ export const SUGESTAO_HOOK = `{
|
|
|
41
41
|
]
|
|
42
42
|
}
|
|
43
43
|
}`;
|
|
44
|
+
/**
|
|
45
|
+
* O que escrever num `AGENTS.md`, para agente que NAO e o Claude Code.
|
|
46
|
+
*
|
|
47
|
+
* O hook de `SessionStart` e um mecanismo do Claude Code: Codex, Cursor, Gemini CLI e
|
|
48
|
+
* Windsurf nao o executam. Nessas ferramentas nada carrega a politica sozinho — e o
|
|
49
|
+
* resultado e a mesma falha de sempre, a sessao abrindo sem protocolo sem avisar.
|
|
50
|
+
*
|
|
51
|
+
* O que resta la e o servidor MCP, que essas ferramentas suportam. Mas ferramenta
|
|
52
|
+
* disponivel nao e ferramenta chamada: sem uma instrucao explicita, o agente pode
|
|
53
|
+
* simplesmente nunca perguntar pela politica. Dai esta linha, que e curta de proposito —
|
|
54
|
+
* ela manda buscar a regra, nao repete a regra.
|
|
55
|
+
*/
|
|
56
|
+
export const SUGESTAO_AGENTS = `# AGENTS.md
|
|
57
|
+
|
|
58
|
+
Este projeto tem um protocolo de trabalho que **não está neste arquivo**.
|
|
59
|
+
|
|
60
|
+
**ANTES de qualquer outra coisa**, chame a ferramenta MCP \`ler_artefato\` com
|
|
61
|
+
\`tipo: "politica"\` e siga o que ela devolver. É a primeira ação da sessão, antes
|
|
62
|
+
de ler código, responder ou planejar.
|
|
63
|
+
|
|
64
|
+
- Se a ferramenta não existir, o servidor MCP do dd-harness não está declarado:
|
|
65
|
+
avise o usuário e **não modifique nada** até ele resolver.
|
|
66
|
+
- Se ela devolver vazio, este projeto nunca foi briefado — diga isso ao usuário.
|
|
67
|
+
|
|
68
|
+
O Claude Code carrega a política sozinho, por hook. Nas outras ferramentas, a
|
|
69
|
+
chamada acima é o que substitui esse hook.`;
|
|
44
70
|
async function declaraMcp(raiz) {
|
|
45
71
|
try {
|
|
46
72
|
const cru = await readFile(join(raiz, ".mcp.json"), "utf8");
|
|
@@ -91,5 +117,22 @@ export async function init(raiz, dados) {
|
|
|
91
117
|
config,
|
|
92
118
|
mcp: await declaraMcp(raiz),
|
|
93
119
|
hook: await declaraHook(raiz),
|
|
120
|
+
agents: await apontaNoAgents(raiz),
|
|
94
121
|
};
|
|
95
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* O `AGENTS.md` ja manda buscar a politica?
|
|
125
|
+
*
|
|
126
|
+
* Procura pela CHAMADA (`ler_artefato`), nao por uma frase exata: quem ja escreveu a
|
|
127
|
+
* instrucao pode te-la redigido de outro jeito, e sugerir de novo por causa de palavra
|
|
128
|
+
* diferente e ruido.
|
|
129
|
+
*/
|
|
130
|
+
async function apontaNoAgents(raiz) {
|
|
131
|
+
try {
|
|
132
|
+
const cru = await readFile(join(raiz, "AGENTS.md"), "utf8");
|
|
133
|
+
return cru.includes("ler_artefato") ? "ja-aponta" : "a-apontar";
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return "a-apontar";
|
|
137
|
+
}
|
|
138
|
+
}
|
package/dist/politica.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* A diferenca entre as duas ultimas e o que o `GET /api/v1/artefatos` responde: `404` e
|
|
16
16
|
* "nao ha projeto"; `200` com `politica` nula e "existe, nunca briefado".
|
|
17
17
|
*/
|
|
18
|
-
export type ResultadoDaPolitica = {
|
|
18
|
+
export type ResultadoDaPolitica = ({
|
|
19
19
|
estado: "ok";
|
|
20
20
|
conteudo: string;
|
|
21
21
|
} | {
|
|
@@ -23,5 +23,11 @@ export type ResultadoDaPolitica = {
|
|
|
23
23
|
} | {
|
|
24
24
|
estado: "inalcancavel";
|
|
25
25
|
motivo: string;
|
|
26
|
+
}) & {
|
|
27
|
+
/**
|
|
28
|
+
* Memorias esperando o worker. Vem de carona no mesmo payload da politica — o hook ja
|
|
29
|
+
* faz esta chamada, entao saber o estado da fila custa zero requisicao.
|
|
30
|
+
*/
|
|
31
|
+
esperandoIndexacao?: number;
|
|
26
32
|
};
|
|
27
33
|
export declare function buscaPolitica(raiz: string): Promise<ResultadoDaPolitica>;
|
package/dist/politica.js
CHANGED
|
@@ -23,10 +23,11 @@ export async function buscaPolitica(raiz) {
|
|
|
23
23
|
}
|
|
24
24
|
const payload = (await resposta.json());
|
|
25
25
|
const conteudo = payload.politica?.trim();
|
|
26
|
+
const esperandoIndexacao = payload.esperando_indexacao ?? 0;
|
|
26
27
|
// Vazio e nulo sao a mesma coisa aqui, e os dois significam "nunca foi escrita".
|
|
27
28
|
if (!conteudo)
|
|
28
|
-
return { estado: "sem-politica" };
|
|
29
|
-
return { estado: "ok", conteudo };
|
|
29
|
+
return { estado: "sem-politica", esperandoIndexacao };
|
|
30
|
+
return { estado: "ok", conteudo, esperandoIndexacao };
|
|
30
31
|
}
|
|
31
32
|
catch (erro) {
|
|
32
33
|
return { estado: "inalcancavel", motivo: mensagem(erro) };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trocar o alvo de uma ancora, sem reescrever a memoria.
|
|
3
|
+
*
|
|
4
|
+
* O caso: uma refatoracao move um arquivo, e toda memoria ancorada nele passa a acusar
|
|
5
|
+
* "alvo ausente". O `check --commit` cruza isso com os renames do git e diz para onde o
|
|
6
|
+
* conteudo foi; este comando aplica a troca.
|
|
7
|
+
*
|
|
8
|
+
* Separado de `editar` de proposito: editar exige o markdown inteiro e mexe no conteudo,
|
|
9
|
+
* que nao e o que mudou aqui. Reancorar troca um endereco e mais nada — misturar as duas
|
|
10
|
+
* coisas convidaria a reescrever o corpo de memoria enquanto se conserta um caminho.
|
|
11
|
+
*/
|
|
12
|
+
export declare function reancora(raiz: string, endereco: string, de: string, para: string): Promise<{
|
|
13
|
+
endereco: string;
|
|
14
|
+
de: string;
|
|
15
|
+
para: string;
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { cabecalhos, credencial, pede, recusa } from "./api.js";
|
|
2
|
+
import {} from "./curar.js";
|
|
3
|
+
/**
|
|
4
|
+
* Trocar o alvo de uma ancora, sem reescrever a memoria.
|
|
5
|
+
*
|
|
6
|
+
* O caso: uma refatoracao move um arquivo, e toda memoria ancorada nele passa a acusar
|
|
7
|
+
* "alvo ausente". O `check --commit` cruza isso com os renames do git e diz para onde o
|
|
8
|
+
* conteudo foi; este comando aplica a troca.
|
|
9
|
+
*
|
|
10
|
+
* Separado de `editar` de proposito: editar exige o markdown inteiro e mexe no conteudo,
|
|
11
|
+
* que nao e o que mudou aqui. Reancorar troca um endereco e mais nada — misturar as duas
|
|
12
|
+
* coisas convidaria a reescrever o corpo de memoria enquanto se conserta um caminho.
|
|
13
|
+
*/
|
|
14
|
+
export async function reancora(raiz, endereco, de, para) {
|
|
15
|
+
const { config, token } = await credencial(raiz);
|
|
16
|
+
const url = new URL(`${config.api}/api/v1/memorias/${endereco}`);
|
|
17
|
+
url.searchParams.set("tenant", config.tenant);
|
|
18
|
+
url.searchParams.set("projeto", config.projeto);
|
|
19
|
+
const leitura = await pede(url, { headers: cabecalhos(token) });
|
|
20
|
+
if (!leitura.ok)
|
|
21
|
+
await recusa(leitura);
|
|
22
|
+
const memoria = (await leitura.json());
|
|
23
|
+
if (!memoria.ancoras.some((a) => a.valor === de)) {
|
|
24
|
+
throw new Error(`${endereco} não tem âncora em "${de}". Âncoras atuais: ` +
|
|
25
|
+
(memoria.ancoras.map((a) => a.valor).join(", ") || "nenhuma"));
|
|
26
|
+
}
|
|
27
|
+
const novas = memoria.ancoras.map((a) => (a.valor === de ? { ...a, valor: para } : a));
|
|
28
|
+
// Reaproveita o PATCH de edicao: ele recebe a memoria inteira, entao mandamos o que ja
|
|
29
|
+
// estava la com a ancora trocada. Uma porta so para escrever memoria, e nao duas.
|
|
30
|
+
const envio = await pede(`${config.api}/api/v1/memorias/${endereco}`, {
|
|
31
|
+
method: "PATCH",
|
|
32
|
+
headers: cabecalhos(token, true),
|
|
33
|
+
body: JSON.stringify({
|
|
34
|
+
tenant: config.tenant,
|
|
35
|
+
projeto: config.projeto,
|
|
36
|
+
titulo: memoria.titulo,
|
|
37
|
+
resumo: memoria.resumo,
|
|
38
|
+
corpo: memoria.corpo,
|
|
39
|
+
dano: memoria.dano,
|
|
40
|
+
invisibilidade: memoria.invisibilidade,
|
|
41
|
+
externalidade: memoria.externalidade,
|
|
42
|
+
ancoras: novas.map((a) => a.valor),
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
if (!envio.ok)
|
|
46
|
+
await recusa(envio);
|
|
47
|
+
return { endereco, de, para };
|
|
48
|
+
}
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subir o worker de embeddings quando ha fila esperando.
|
|
3
|
+
*
|
|
4
|
+
* O worker nao esta hospedado (decisao de custo, registrada no ROADMAP): roda local, a
|
|
5
|
+
* mao. O problema disso e a degradacao SILENCIOSA — a memoria e gravada, a busca responde
|
|
6
|
+
* `semantica: true` porque a CONSULTA foi vetorizada, e mesmo assim nao acha nada, porque
|
|
7
|
+
* a BASE ainda nao tem vetor. Quem procura conclui "nao existe" quando o certo era
|
|
8
|
+
* "ainda nao indexei".
|
|
9
|
+
*
|
|
10
|
+
* Por isso o hook de sessao sobe o worker sozinho. Duas travas, e as duas importam:
|
|
11
|
+
*
|
|
12
|
+
* - **So sobe quando ha fila.** O hook roda em TODA sessao, inclusive nas que so leem
|
|
13
|
+
* codigo. Subir com a fila vazia gastaria chamada de embedding sem ninguem ter pedido.
|
|
14
|
+
* - **So no repositorio que TEM o worker.** Ele vive neste monorepo, nao no repositorio
|
|
15
|
+
* consumidor: la o `pnpm worker` nao existe, e tentar rodar daria erro a cada sessao.
|
|
16
|
+
*/
|
|
17
|
+
/** O worker vive aqui dentro. Noutro repositorio, nao ha o que subir. */
|
|
18
|
+
export declare function temWorkerLocal(raiz: string): Promise<boolean>;
|
|
19
|
+
export type Subida = {
|
|
20
|
+
subiu: true;
|
|
21
|
+
} | {
|
|
22
|
+
subiu: false;
|
|
23
|
+
motivo: "sem-fila" | "sem-worker" | "falhou";
|
|
24
|
+
detalhe?: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Dispara um lote e devolve na hora — nao espera terminar.
|
|
28
|
+
*
|
|
29
|
+
* `--uma-vez` e nao o modo continuo: o hook de sessao nao pode deixar processo de pe que
|
|
30
|
+
* ninguem mandou subir, e um lote basta para o caso comum (as memorias gravadas na sessao
|
|
31
|
+
* anterior). Fila grande volta a aparecer na proxima sessao, e ai o numero cresce em vez
|
|
32
|
+
* de sumir — que e o sinal de que esta na hora de hospedar de verdade.
|
|
33
|
+
*
|
|
34
|
+
* `unref()` solta o processo do pai: sem isso o hook so retornaria quando o worker
|
|
35
|
+
* terminasse, e a sessao ficaria esperando embedding para abrir.
|
|
36
|
+
*/
|
|
37
|
+
export declare function subiuOWorker(raiz: string, esperando: number): Promise<Subida>;
|
package/dist/worker.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Subir o worker de embeddings quando ha fila esperando.
|
|
6
|
+
*
|
|
7
|
+
* O worker nao esta hospedado (decisao de custo, registrada no ROADMAP): roda local, a
|
|
8
|
+
* mao. O problema disso e a degradacao SILENCIOSA — a memoria e gravada, a busca responde
|
|
9
|
+
* `semantica: true` porque a CONSULTA foi vetorizada, e mesmo assim nao acha nada, porque
|
|
10
|
+
* a BASE ainda nao tem vetor. Quem procura conclui "nao existe" quando o certo era
|
|
11
|
+
* "ainda nao indexei".
|
|
12
|
+
*
|
|
13
|
+
* Por isso o hook de sessao sobe o worker sozinho. Duas travas, e as duas importam:
|
|
14
|
+
*
|
|
15
|
+
* - **So sobe quando ha fila.** O hook roda em TODA sessao, inclusive nas que so leem
|
|
16
|
+
* codigo. Subir com a fila vazia gastaria chamada de embedding sem ninguem ter pedido.
|
|
17
|
+
* - **So no repositorio que TEM o worker.** Ele vive neste monorepo, nao no repositorio
|
|
18
|
+
* consumidor: la o `pnpm worker` nao existe, e tentar rodar daria erro a cada sessao.
|
|
19
|
+
*/
|
|
20
|
+
/** O worker vive aqui dentro. Noutro repositorio, nao ha o que subir. */
|
|
21
|
+
export async function temWorkerLocal(raiz) {
|
|
22
|
+
try {
|
|
23
|
+
await access(join(raiz, "src", "worker", "indexador.ts"));
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Dispara um lote e devolve na hora — nao espera terminar.
|
|
32
|
+
*
|
|
33
|
+
* `--uma-vez` e nao o modo continuo: o hook de sessao nao pode deixar processo de pe que
|
|
34
|
+
* ninguem mandou subir, e um lote basta para o caso comum (as memorias gravadas na sessao
|
|
35
|
+
* anterior). Fila grande volta a aparecer na proxima sessao, e ai o numero cresce em vez
|
|
36
|
+
* de sumir — que e o sinal de que esta na hora de hospedar de verdade.
|
|
37
|
+
*
|
|
38
|
+
* `unref()` solta o processo do pai: sem isso o hook so retornaria quando o worker
|
|
39
|
+
* terminasse, e a sessao ficaria esperando embedding para abrir.
|
|
40
|
+
*/
|
|
41
|
+
export async function subiuOWorker(raiz, esperando) {
|
|
42
|
+
if (esperando <= 0)
|
|
43
|
+
return { subiu: false, motivo: "sem-fila" };
|
|
44
|
+
if (!(await temWorkerLocal(raiz))) {
|
|
45
|
+
return { subiu: false, motivo: "sem-worker" };
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const filho = spawn("npx", ["pnpm@latest", "worker", "--uma-vez"], {
|
|
49
|
+
cwd: raiz,
|
|
50
|
+
detached: true,
|
|
51
|
+
stdio: "ignore",
|
|
52
|
+
shell: process.platform === "win32",
|
|
53
|
+
windowsHide: true,
|
|
54
|
+
});
|
|
55
|
+
filho.unref();
|
|
56
|
+
return { subiu: true };
|
|
57
|
+
}
|
|
58
|
+
catch (erro) {
|
|
59
|
+
return {
|
|
60
|
+
subiu: false,
|
|
61
|
+
motivo: "falhou",
|
|
62
|
+
detalhe: erro instanceof Error ? erro.message : String(erro),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
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
6
|
"license": "UNLICENSED",
|