dd-harness 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brain.d.ts +55 -0
- package/dist/brain.js +9 -0
- package/dist/buscar.d.ts +11 -4
- package/dist/buscar.js +6 -1
- package/dist/check.d.ts +7 -2
- package/dist/check.js +21 -5
- package/dist/curar.d.ts +38 -0
- package/dist/curar.js +39 -29
- package/dist/diff.d.ts +60 -3
- package/dist/diff.js +74 -16
- package/dist/index.js +217 -121
- package/dist/init.d.ts +40 -4
- package/dist/init.js +97 -43
- 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 +44 -44
package/dist/brain.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
/**
|
|
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;
|
|
55
|
+
};
|
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/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,5 +1,5 @@
|
|
|
1
|
-
import { type Tocada } from "./diff.js";
|
|
2
|
-
import type { Brain } from "./
|
|
1
|
+
import { type Reancoragem, type Tocada } from "./diff.js";
|
|
2
|
+
import type { Brain } from "./brain.js";
|
|
3
3
|
/**
|
|
4
4
|
* `dd-harness check` — resolve as âncoras contra a árvore de trabalho.
|
|
5
5
|
*
|
|
@@ -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/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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Brain } from "./
|
|
1
|
+
import type { Brain } from "./brain.js";
|
|
2
2
|
/**
|
|
3
3
|
* Quais memorias falam do que este commit mudou.
|
|
4
4
|
*
|
|
@@ -10,14 +10,71 @@ import type { Brain } from "./materializa.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.
|