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.
@@ -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 abre o arquivo que o `sync` ja
9
- * materializou. Assim a busca continua barata e o Brain em disco continua sendo a fonte.
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 declare function busca(raiz: string, consulta: string, limite?: number): Promise<{
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 { semantica: lido.semantica, provedor: lido.provedor, achados: lido.resultados };
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 "./materializa.js";
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 tocadas = commit
84
- ? memoriasTocadas(brain, await caminhosDoCommit(raiz, commit))
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 { medidas: 0, ausentes: [], novas: 0, base: 0, jaAbertas: 0, fechadas: 0, tocadas };
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
- * Curadoria pelo agente: editar e arquivar.
5
+ * A memoria inteira, no markdown que `gravar` e `editar` consomem.
8
6
  *
9
- * `gravar` sabia criar e mais nada. Uma memoria errada ficava errada, porque corrigir
10
- * exigia abrir a interface e o `CLAUDE.md` trata curadoria como obrigacao ("se
11
- * encontrar uma memoria obsoleta ou errada, corrija").
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 marcaComoEnviado(raiz, caminho, conteudo) {
27
- const chave = relative(raiz, caminho).split("\\").join("/");
28
- const manifesto = await leManifesto(raiz);
29
- // Arquivo de fora do repositorio (um rascunho em /tmp, por exemplo) nao esta no
30
- // manifesto e nao deve entrar: o manifesto descreve o que o `sync` materializou.
31
- if (!manifesto.arquivos[chave])
32
- return;
33
- manifesto.arquivos[chave] = hashDe(conteudo);
34
- await gravaManifesto(raiz, manifesto);
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 "./materializa.js";
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
- /** Caminhos alterados num commit. Vazio quando o git nao responde nunca quebra. */
14
- export declare function caminhosDoCommit(raiz: string, commit: string): Promise<string[]>;
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
- * Quais memorias falam do que este commit mudou.
5
+ * O que um commit mudou: caminhos tocados e renames detectados.
6
6
  *
7
- * E a peca que devolve a memoria ao code review. Tirar o Brain do repositorio tirou o
8
- * revisor que o diff dava de graca no modelo file-based a memoria estava ali, no
9
- * caminho de quem mexia. Aqui ela volta pelo cruzamento: caminho do diff contra ancora.
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
- * Repare que isto NAO e deriva. Deriva e "o alvo mudou desde a linha de base"; isto e
12
- * "voce acabou de mexer no que esta memoria guarda" — util mesmo quando a base ainda
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", "--name-only", "-r", commit], { cwd: raiz });
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
- // Sem git, commit inexistente, repositorio raso: o cruzamento simplesmente nao
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.