@fontdata/ai-harness 3.0.0 → 3.1.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/.claude-plugin/plugin.json +2 -2
- package/README.md +7 -6
- package/dist/banco-dados/ambiente.js +17 -3
- package/dist/banco-dados/command.js +58 -20
- package/dist/banco-dados/conexao.js +1 -1
- package/dist/banco-dados/ferramentas.js +3 -4
- package/dist/config/config.js +24 -0
- package/dist/doctor/checks.js +2 -2
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fontdata",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"mcpServers": {
|
|
5
|
-
"banco_dados": { "command": "npx", "args": ["-y", "@fontdata/ai-harness@3.
|
|
5
|
+
"banco_dados": { "command": "npx", "args": ["-y", "@fontdata/ai-harness@3.1.0", "banco-dados-mcp"] }
|
|
6
6
|
},
|
|
7
7
|
"description": "Harness de engenharia com IA da Fontdata para Claude Code",
|
|
8
8
|
"author": { "name": "Fontdata" },
|
package/README.md
CHANGED
|
@@ -129,15 +129,16 @@ da conexão e o nome da variável de ambiente (`dsn_env`), e cada dev guarda o D
|
|
|
129
129
|
`.claude/settings.local.json` (fora do git), no `.env` ou no ambiente:
|
|
130
130
|
|
|
131
131
|
```bash
|
|
132
|
-
npx @fontdata/ai-harness banco-dados adicionar erp
|
|
132
|
+
npx @fontdata/ai-harness banco-dados adicionar erp # na raiz do projeto: pergunta os dados, testa e grava ERP_DATABASE_URL
|
|
133
133
|
npx @fontdata/ai-harness banco-dados testar
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
O `adicionar
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
O `adicionar` grava `{ "nome": "erp", "dsn_env": "ERP_DATABASE_URL" }` em `banco_dados.conexoes` e a variável no
|
|
137
|
+
`.claude/settings.local.json`; fora da raiz de um projeto (sem `.git` nem `.ai-harness/config.json`), ele recusa sem
|
|
138
|
+
gravar nada. Faça commit do `config.json`; cada colega roda o mesmo comando, que grava só a variável.
|
|
139
|
+
`"producao": true` na entrada vale para a equipe toda, e `banco-dados remover erp` tira a conexão do projeto. Quem
|
|
140
|
+
prefere um cadastro por máquina (`~/.config/ai-harness/conexoes.json`) usa `banco-dados adicionar erp-dev --global` e
|
|
141
|
+
`banco-dados usar erp-dev` no projeto.
|
|
141
142
|
|
|
142
143
|
O servidor MCP `banco_dados` entrega ao Claude `listar_conexoes`, `listar_tabelas`, `descrever_tabela` e
|
|
143
144
|
`consultar`, tudo somente leitura. Ele vem no plugin; sem plugin, o `init` e o `update` o registram no `.mcp.json`
|
|
@@ -81,12 +81,27 @@ export function gravarVariavelSettingsLocal(raiz, nome, valor) {
|
|
|
81
81
|
const envAtual = ehObjeto(dados.env) ? dados.env : {};
|
|
82
82
|
if (envAtual[nome] === valor)
|
|
83
83
|
return { changed: false };
|
|
84
|
-
const novo = { ...dados, env: { ...envAtual, [nome]: valor } };
|
|
85
84
|
const modo = leitura.existe ? statSync(caminho).mode & 0o777 : 0o600;
|
|
86
85
|
mkdirSync(dirname(caminho), { recursive: true });
|
|
86
|
+
gravarAtomico(caminho, { ...dados, env: { ...envAtual, [nome]: valor } }, modo);
|
|
87
|
+
return { changed: true };
|
|
88
|
+
}
|
|
89
|
+
/** Tira a variável do env do settings.local.json com a mesma gravação atômica; arquivo ou chave ausente não escreve. */
|
|
90
|
+
export function removerVariavelSettingsLocal(raiz, nome) {
|
|
91
|
+
const caminho = join(raiz, SETTINGS_LOCAL_PATH);
|
|
92
|
+
const leitura = lerSettings(caminho);
|
|
93
|
+
if ('erro' in leitura)
|
|
94
|
+
throw new Error(leitura.erro);
|
|
95
|
+
if (!leitura.existe || !ehObjeto(leitura.dados.env) || !Object.hasOwn(leitura.dados.env, nome))
|
|
96
|
+
return { changed: false };
|
|
97
|
+
const { [nome]: _removida, ...envRestante } = leitura.dados.env;
|
|
98
|
+
gravarAtomico(caminho, { ...leitura.dados, env: envRestante }, statSync(caminho).mode & 0o777);
|
|
99
|
+
return { changed: true };
|
|
100
|
+
}
|
|
101
|
+
function gravarAtomico(caminho, dados, modo) {
|
|
87
102
|
const temporario = `${caminho}.${process.pid}.${Date.now()}.tmp`;
|
|
88
103
|
try {
|
|
89
|
-
writeFileSync(temporario, `${JSON.stringify(
|
|
104
|
+
writeFileSync(temporario, `${JSON.stringify(dados, null, 2)}\n`, { mode: modo });
|
|
90
105
|
chmodSync(temporario, modo);
|
|
91
106
|
renameSync(temporario, caminho);
|
|
92
107
|
}
|
|
@@ -94,5 +109,4 @@ export function gravarVariavelSettingsLocal(raiz, nome, valor) {
|
|
|
94
109
|
rmSync(temporario, { force: true });
|
|
95
110
|
throw erro;
|
|
96
111
|
}
|
|
97
|
-
return { changed: true };
|
|
98
112
|
}
|
|
@@ -2,24 +2,28 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { EOL } from 'node:os';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
import * as p from '@clack/prompts';
|
|
5
|
-
import { comSecaoBanco, CONFIG_PATH, gravarConexaoProjeto, readConfig, REGRA_DSN_ENV, REGRA_NOME_CONEXAO, secaoBanco, validarConfigParaEscrita, writeDefaultConfig, } from '../config/config.js';
|
|
5
|
+
import { comSecaoBanco, CONFIG_PATH, gravarConexaoProjeto, readConfig, removerConexaoProjeto, REGRA_DSN_ENV, REGRA_NOME_CONEXAO, secaoBanco, validarConfigParaEscrita, writeDefaultConfig, } from '../config/config.js';
|
|
6
6
|
import { suggestFlag } from '../install/flags.js';
|
|
7
7
|
import { caminhoCadastro, lerCadastro, ocultarSenha, removerConexao, resumirConexao, salvarConexao, TIPOS_BANCO, } from './cadastro.js';
|
|
8
|
-
import { gravarVariavelSettingsLocal, lerEnvSettingsLocal, lerVariavel, SETTINGS_LOCAL_PATH, validarSettingsLocal } from './ambiente.js';
|
|
8
|
+
import { gravarVariavelSettingsLocal, lerEnvSettingsLocal, lerVariavel, removerVariavelSettingsLocal, SETTINGS_LOCAL_PATH, validarSettingsLocal, } from './ambiente.js';
|
|
9
9
|
import { interpretarDsn, resolverConexoes } from './conexao.js';
|
|
10
10
|
import { criarDriver as criarDriverPadrao } from './drivers/index.js';
|
|
11
11
|
const AJUDA = [
|
|
12
12
|
'Uso: ai-harness banco-dados <subcomando>',
|
|
13
13
|
'',
|
|
14
|
-
' adicionar <nome> [--dsn <dsn>] [--producao] [--sem-testar] [--substituir]',
|
|
15
|
-
'
|
|
16
|
-
' adicionar <nome> --projeto [--env <VAR>] [--dsn <dsn>] [--producao] [--sem-testar] [--substituir]',
|
|
17
|
-
' grava o DSN na variável <NOME>_DATABASE_URL (ou --env) do .claude/settings.local.json',
|
|
14
|
+
' adicionar <nome> [--env <VAR>] [--dsn <dsn>] [--producao] [--sem-testar] [--substituir]',
|
|
15
|
+
' grava no projeto: o DSN na variável <NOME>_DATABASE_URL (ou --env) do .claude/settings.local.json',
|
|
18
16
|
' e a conexão { nome, dsn_env } em banco_dados.conexoes do .ai-harness/config.json',
|
|
17
|
+
' (sem --dsn, pergunta os dados)',
|
|
18
|
+
' adicionar <nome> --global [--dsn <dsn>] [--producao] [--sem-testar] [--substituir]',
|
|
19
|
+
' cadastra a conexão nesta máquina',
|
|
19
20
|
' listar mostra as conexões cadastradas e as do projeto, sem a senha',
|
|
20
21
|
' testar [nome] testa a conexão pedida ou todas as do projeto',
|
|
21
|
-
' usar <nome...> grava banco_dados.conexoes no .ai-harness/config.json
|
|
22
|
-
'
|
|
22
|
+
' usar <nome...> grava banco_dados.conexoes no .ai-harness/config.json, para conexões do cadastro global',
|
|
23
|
+
' (mantém as entradas { nome, dsn_env })',
|
|
24
|
+
' remover <nome> [--global]',
|
|
25
|
+
' tira a conexão do projeto e a variável dela do .claude/settings.local.json;',
|
|
26
|
+
' com --global, apaga a conexão do cadastro desta máquina',
|
|
23
27
|
].join(EOL);
|
|
24
28
|
const PORTA_PADRAO = { postgres: '5432', mariadb: '3306' };
|
|
25
29
|
const ESQUEMA = { postgres: 'postgres', mariadb: 'mysql' };
|
|
@@ -118,14 +122,15 @@ function conexaoDoDsn(dsn, cwd, producao) {
|
|
|
118
122
|
return conexao;
|
|
119
123
|
}
|
|
120
124
|
async function adicionar(ctx, argv) {
|
|
121
|
-
const { posicionais, marcadas, valores } = lerArgumentos(argv, ['--producao', '--sem-testar', '--substituir', '--
|
|
125
|
+
const { posicionais, marcadas, valores } = lerArgumentos(argv, ['--producao', '--sem-testar', '--substituir', '--global'], ['--dsn', '--env']);
|
|
122
126
|
const [nome, ...sobra] = posicionais;
|
|
123
|
-
if (!nome || sobra.length > 0)
|
|
124
|
-
throw new Error('Uso: ai-harness banco-dados adicionar <nome> [--
|
|
125
|
-
|
|
127
|
+
if (!nome || sobra.length > 0) {
|
|
128
|
+
throw new Error('Uso: ai-harness banco-dados adicionar <nome> [--global | --env <VAR>] [--dsn <dsn>] [--producao] [--sem-testar] [--substituir]');
|
|
129
|
+
}
|
|
130
|
+
if (!marcadas.has('--global'))
|
|
126
131
|
return adicionarNoProjeto(ctx, nome, marcadas, valores);
|
|
127
132
|
if (valores['--env'] !== undefined)
|
|
128
|
-
throw new Error('--env
|
|
133
|
+
throw new Error('--env não vale com --global.');
|
|
129
134
|
const substituir = marcadas.has('--substituir');
|
|
130
135
|
if (!substituir && nome in lerCadastro(ctx.cadastro).conexoes) {
|
|
131
136
|
ctx.erro(`A conexão ${nome} já existe em ${ctx.cadastro}. Use --substituir para trocá-la.`);
|
|
@@ -169,6 +174,10 @@ function variavelPadrao(nome) {
|
|
|
169
174
|
return `${nome.toUpperCase().replace(/-/g, '_')}_DATABASE_URL`;
|
|
170
175
|
}
|
|
171
176
|
async function adicionarNoProjeto(ctx, nome, marcadas, valores) {
|
|
177
|
+
if (!existsSync(join(ctx.cwd, '.git')) && !existsSync(join(ctx.cwd, CONFIG_PATH))) {
|
|
178
|
+
ctx.erro(`Nenhum projeto em ${ctx.cwd} (sem .git nem ${CONFIG_PATH}). Rode na raiz do projeto ou use --global para cadastrar nesta máquina.`);
|
|
179
|
+
return 1;
|
|
180
|
+
}
|
|
172
181
|
const substituir = marcadas.has('--substituir');
|
|
173
182
|
if (!REGRA_NOME_CONEXAO.test(nome)) {
|
|
174
183
|
ctx.erro(`Nome de conexão inválido: ${JSON.stringify(nome)}; use só minúsculas, dígitos, - e _, começando por minúscula ou dígito.`);
|
|
@@ -260,7 +269,7 @@ function listar(ctx, argv) {
|
|
|
260
269
|
const doProjeto = config.banco_dados.conexoes.flatMap((nome) => config.banco_dados.projeto[nome] ?? []);
|
|
261
270
|
const nomes = Object.keys(conexoes).sort();
|
|
262
271
|
if (nomes.length === 0 && doProjeto.length === 0) {
|
|
263
|
-
ctx.log(
|
|
272
|
+
ctx.log('Nenhuma conexão. Na raiz do projeto, rode: npx @fontdata/ai-harness banco-dados adicionar <nome>');
|
|
264
273
|
return 0;
|
|
265
274
|
}
|
|
266
275
|
for (const nome of nomes) {
|
|
@@ -313,7 +322,7 @@ async function testar(ctx, argv) {
|
|
|
313
322
|
for (const problema of resolvidas.problemas)
|
|
314
323
|
ctx.erro(problema);
|
|
315
324
|
for (const faltando of resolvidas.faltando.filter((item) => nome === undefined || item === nome)) {
|
|
316
|
-
ctx.erro(`Conexão ${faltando} não encontrada nesta máquina; rode banco-dados adicionar ${faltando}.`);
|
|
325
|
+
ctx.erro(`Conexão ${faltando} não encontrada nesta máquina; rode banco-dados adicionar ${faltando} --global.`);
|
|
317
326
|
ok = false;
|
|
318
327
|
}
|
|
319
328
|
for (const ausente of resolvidas.variaveisAusentes.filter(pedida)) {
|
|
@@ -327,7 +336,7 @@ async function testar(ctx, argv) {
|
|
|
327
336
|
}
|
|
328
337
|
alvos = resolvidas.conexoes.filter(pedida);
|
|
329
338
|
if (alvos.length === 0 && ok) {
|
|
330
|
-
ctx.erro('Nenhuma conexão de banco configurada neste projeto; rode npx @fontdata/ai-harness banco-dados
|
|
339
|
+
ctx.erro('Nenhuma conexão de banco configurada neste projeto; rode npx @fontdata/ai-harness banco-dados adicionar <nome>.');
|
|
331
340
|
return 1;
|
|
332
341
|
}
|
|
333
342
|
}
|
|
@@ -371,15 +380,17 @@ function usar(ctx, argv) {
|
|
|
371
380
|
const { conexoes } = lerCadastro(ctx.cadastro);
|
|
372
381
|
const ausentes = unicos.filter((nome) => !(nome in conexoes) && !objetos.has(nome));
|
|
373
382
|
if (ausentes.length > 0) {
|
|
374
|
-
ctx.erro(`Aviso: sem cadastro nesta máquina para ${ausentes.join(', ')}; rode banco-dados adicionar <nome> para cada uma.`);
|
|
383
|
+
ctx.erro(`Aviso: sem cadastro nesta máquina para ${ausentes.join(', ')}; rode banco-dados adicionar <nome> --global para cada uma.`);
|
|
375
384
|
}
|
|
376
385
|
return 0;
|
|
377
386
|
}
|
|
378
387
|
function remover(ctx, argv) {
|
|
379
|
-
const { posicionais } = lerArgumentos(argv, []);
|
|
388
|
+
const { posicionais, marcadas } = lerArgumentos(argv, ['--global']);
|
|
380
389
|
const [nome, ...sobra] = posicionais;
|
|
381
390
|
if (!nome || sobra.length > 0)
|
|
382
|
-
throw new Error('Uso: ai-harness banco-dados remover <nome>');
|
|
391
|
+
throw new Error('Uso: ai-harness banco-dados remover <nome> [--global]');
|
|
392
|
+
if (!marcadas.has('--global'))
|
|
393
|
+
return removerDoProjeto(ctx, nome);
|
|
383
394
|
if (!removerConexao(nome, ctx.cadastro)) {
|
|
384
395
|
ctx.erro(`Conexão ${nome} não existe em ${ctx.cadastro}.`);
|
|
385
396
|
return 1;
|
|
@@ -387,7 +398,34 @@ function remover(ctx, argv) {
|
|
|
387
398
|
ctx.log(`Conexão ${nome} removida de ${ctx.cadastro}.`);
|
|
388
399
|
return 0;
|
|
389
400
|
}
|
|
390
|
-
|
|
401
|
+
function removerDoProjeto(ctx, nome) {
|
|
402
|
+
validarSettingsLocal(ctx.cwd);
|
|
403
|
+
validarConfigParaEscrita(ctx.cwd);
|
|
404
|
+
const removida = removerConexaoProjeto(ctx.cwd, nome);
|
|
405
|
+
if (removida === null) {
|
|
406
|
+
const noCadastro = nome in lerCadastro(ctx.cadastro).conexoes;
|
|
407
|
+
ctx.erro(`Conexão ${nome} não está em banco_dados.conexoes de ${CONFIG_PATH}.${noCadastro ? ` Para o cadastro desta máquina, use remover ${nome} --global.` : ''}`);
|
|
408
|
+
return 1;
|
|
409
|
+
}
|
|
410
|
+
ctx.log(`Conexão ${nome} removida de banco_dados.conexoes de ${CONFIG_PATH}.`);
|
|
411
|
+
const variavel = typeof removida === 'string' ? undefined : removida.dsn_env;
|
|
412
|
+
if (variavel === undefined)
|
|
413
|
+
return 0;
|
|
414
|
+
const atual = lerEnvSettingsLocal(ctx.cwd)[variavel];
|
|
415
|
+
if (atual !== undefined)
|
|
416
|
+
ctx.sensiveis.push(sensivel(atual));
|
|
417
|
+
const outra = Object.values(readConfig(ctx.cwd).config.banco_dados.projeto).find((conexao) => conexao.dsn_env === variavel);
|
|
418
|
+
if (outra)
|
|
419
|
+
ctx.log(`Variável ${variavel} mantida em ${SETTINGS_LOCAL_PATH}: a conexão ${outra.nome} também a usa.`);
|
|
420
|
+
else if (removerVariavelSettingsLocal(ctx.cwd, variavel).changed)
|
|
421
|
+
ctx.log(`Variável ${variavel} removida de ${SETTINGS_LOCAL_PATH}.`);
|
|
422
|
+
ctx.log(`Se ${variavel} estiver definida no .env ou no ambiente, ela não foi tocada.`);
|
|
423
|
+
return 0;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* `ai-harness banco-dados <subcomando>`: conexões do projeto (padrão), cadastro global de conexões da máquina
|
|
427
|
+
* (`--global`) e escolha das conexões do cadastro que o projeto usa.
|
|
428
|
+
*/
|
|
391
429
|
export async function runBancoCommand(argv, opcoes = {}) {
|
|
392
430
|
const saida = opcoes.saida ?? { log: (texto) => console.log(texto), erro: (texto) => console.error(texto) };
|
|
393
431
|
const cadastro = opcoes.caminhoCadastro ?? caminhoCadastro();
|
|
@@ -118,7 +118,7 @@ export function resolverConexoes(raiz, opcoes = {}) {
|
|
|
118
118
|
}
|
|
119
119
|
export function escolherConexao(resolvidas, nome) {
|
|
120
120
|
if (resolvidas.length === 0) {
|
|
121
|
-
throw new Error('Nenhuma conexão de banco configurada neste projeto; rode npx @fontdata/ai-harness banco-dados
|
|
121
|
+
throw new Error('Nenhuma conexão de banco configurada neste projeto; rode npx @fontdata/ai-harness banco-dados adicionar <nome> na raiz do projeto');
|
|
122
122
|
}
|
|
123
123
|
const escolhida = nome === undefined ? resolvidas[0] : resolvidas.find((conexao) => conexao.nome === nome);
|
|
124
124
|
if (!escolhida)
|
|
@@ -55,16 +55,15 @@ export const INSTRUCOES = [
|
|
|
55
55
|
'Use estas ferramentas para ler o banco de dados do projeto; o acesso é somente leitura.',
|
|
56
56
|
'Nunca peça nem repita credenciais, senhas ou DSNs no chat.',
|
|
57
57
|
'As conexões vêm do cadastro da máquina ou do projeto: uma conexão do projeto é declarada em banco_dados.conexoes do .ai-harness/config.json com dsn_env (o nome da variável), e o valor da variável fica no .claude/settings.local.json, no .env ou no ambiente.',
|
|
58
|
-
'Sem conexão configurada, oriente o usuário a rodar
|
|
58
|
+
'Sem conexão configurada, oriente o usuário a rodar na raiz do projeto `npx @fontdata/ai-harness banco-dados adicionar <nome>`.',
|
|
59
59
|
'Se a variável de uma conexão do projeto não estiver definida, oriente a defini-la no .claude/settings.local.json, no .env ou no ambiente.',
|
|
60
60
|
'Antes de consultar, rode listar_tabelas e descrever_tabela para conhecer o esquema.',
|
|
61
61
|
'Em consultar, selecione só as colunas necessárias e filtre as linhas; evite SELECT *.',
|
|
62
62
|
'Somente leitura: comandos que alteram dados ou esquema são recusados.',
|
|
63
63
|
'Os dados pessoais (CPF, e-mail, telefone, nome e afins) vêm mascarados, e o rodapé de consultar diz o que foi mascarado. Não tente contornar o mascaramento: não concatene, fatie, converta nem compare valores para reconstruir o original. Peça só as colunas necessárias.',
|
|
64
64
|
].join('\n');
|
|
65
|
-
const ORIENTACAO_USAR = 'rode npx @fontdata/ai-harness banco-dados usar <nome> no projeto';
|
|
66
65
|
function orientarAdicionar(nome) {
|
|
67
|
-
return `Conexão ${nome} não encontrada nesta máquina; rode npx @fontdata/ai-harness banco-dados adicionar ${nome}`;
|
|
66
|
+
return `Conexão ${nome} não encontrada nesta máquina; rode npx @fontdata/ai-harness banco-dados adicionar ${nome} --global`;
|
|
68
67
|
}
|
|
69
68
|
function orientarVariavel(nome, variavel) {
|
|
70
69
|
return `A variável ${variavel} da conexão ${nome} não está definida; defina-a no .claude/settings.local.json, no .env ou no ambiente`;
|
|
@@ -173,7 +172,7 @@ async function executar(nome, args, contexto, banco, resolvidas) {
|
|
|
173
172
|
if (descartadas.length > 0)
|
|
174
173
|
dados.descartadas = descartadas;
|
|
175
174
|
if (conexoes.length + faltando.length + variaveisAusentes.length + descartadas.length === 0) {
|
|
176
|
-
dados.orientacao =
|
|
175
|
+
dados.orientacao = 'Nenhuma conexão configurada; rode npx @fontdata/ai-harness banco-dados adicionar <nome> na raiz do projeto';
|
|
177
176
|
}
|
|
178
177
|
if (resolvidas.problemas.length > 0)
|
|
179
178
|
dados.problemas = resolvidas.problemas;
|
package/dist/config/config.js
CHANGED
|
@@ -301,6 +301,30 @@ export function gravarConexaoProjeto(root, conexao, { substituir = false } = {})
|
|
|
301
301
|
const base = raw ?? lerJsonParaEscrita(file, 'banco_dados.conexoes');
|
|
302
302
|
writeFileSync(file, `${JSON.stringify(comSecaoBanco(base, { ...banco, conexoes: proximas }), null, 2)}\n`);
|
|
303
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Tira de `banco_dados.conexoes` a primeira entrada com o nome (objeto, mesmo inválido, ou texto), preservando
|
|
306
|
+
* o resto do JSON, e devolve o que leu. Sem config ou sem o nome, devolve null e não escreve.
|
|
307
|
+
* JSON inválido ou `conexoes` que não é lista lança sem tocar no arquivo.
|
|
308
|
+
*/
|
|
309
|
+
export function removerConexaoProjeto(root, nome) {
|
|
310
|
+
const file = join(root, CONFIG_PATH);
|
|
311
|
+
if (!existsSync(file))
|
|
312
|
+
return null;
|
|
313
|
+
const raw = lerJsonParaEscrita(file, 'banco_dados.conexoes');
|
|
314
|
+
const banco = secaoBanco(raw);
|
|
315
|
+
const conexoes = banco.conexoes === undefined ? [] : banco.conexoes;
|
|
316
|
+
if (!Array.isArray(conexoes))
|
|
317
|
+
throw new Error(`banco_dados.conexoes em ${CONFIG_PATH} não é uma lista; corrija-o antes de remover a conexão ${nome}.`);
|
|
318
|
+
const posicao = conexoes.findIndex((entrada) => (isObject(entrada) ? entrada.nome : entrada) === nome);
|
|
319
|
+
if (posicao === -1)
|
|
320
|
+
return null;
|
|
321
|
+
const entrada = conexoes[posicao];
|
|
322
|
+
const proximas = conexoes.filter((_atual, i) => i !== posicao);
|
|
323
|
+
writeFileSync(file, `${JSON.stringify(comSecaoBanco(raw, { ...banco, conexoes: proximas }), null, 2)}\n`);
|
|
324
|
+
if (!isObject(entrada))
|
|
325
|
+
return nome;
|
|
326
|
+
return typeof entrada.dsn_env === 'string' ? { nome, dsn_env: entrada.dsn_env } : { nome };
|
|
327
|
+
}
|
|
304
328
|
/**
|
|
305
329
|
* Grava só `banco_dados.registrar_mcp`, preservando o resto do JSON; sem config, cria uma com os padrões
|
|
306
330
|
* e essa chave. JSON inválido é recusado sem tocar no arquivo.
|
package/dist/doctor/checks.js
CHANGED
|
@@ -425,7 +425,7 @@ function checkBancoProjeto(root, config, env) {
|
|
|
425
425
|
id: 'banco-variavel-ausente',
|
|
426
426
|
severidade: 'aviso',
|
|
427
427
|
mensagem: `A conexão ${nome}, declarada em banco_dados.conexoes de ${join(root, CONFIG_PATH)}, lê o DSN da variável ${dsn_env}, que não está definida no ambiente, em ${SETTINGS_LOCAL_PATH} nem no .env.`,
|
|
428
|
-
correcao: `Defina ${dsn_env} com \`npx @fontdata/ai-harness banco-dados adicionar ${nome}
|
|
428
|
+
correcao: `Defina ${dsn_env} com \`npx @fontdata/ai-harness banco-dados adicionar ${nome}\` ou no ambiente do terminal.`,
|
|
429
429
|
});
|
|
430
430
|
}
|
|
431
431
|
const envLocal = lerEnvSettingsLocal(root);
|
|
@@ -487,7 +487,7 @@ export function checkBanco(root, config, env = process.env) {
|
|
|
487
487
|
id: 'banco-conexao-ausente',
|
|
488
488
|
severidade: 'aviso',
|
|
489
489
|
mensagem: `A conexão ${nome}, citada em banco_dados.conexoes de ${join(root, CONFIG_PATH)}, não está no cadastro desta máquina (${caminho}).`,
|
|
490
|
-
correcao: `Cadastre-a com \`npx @fontdata/ai-harness banco-dados adicionar ${nome}\`.`,
|
|
490
|
+
correcao: `Cadastre-a com \`npx @fontdata/ai-harness banco-dados adicionar ${nome} --global\`.`,
|
|
491
491
|
});
|
|
492
492
|
}
|
|
493
493
|
for (const nome of doCadastro.filter((nome) => conexoes[nome]?.mascaramento === false)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fontdata/ai-harness",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Harness de engenharia com IA da Fontdata para Claude Code: roda os slash commands direto na pasta do projeto via npx, sem instalar nada — ou instala-os em .claude/commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|