@fontdata/ai-harness 2.7.0 → 3.0.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 +12 -12
- package/dist/{banco → banco-dados}/command.js +30 -30
- package/dist/{banco → banco-dados}/conexao.js +4 -4
- package/dist/{banco → banco-dados}/ferramentas.js +10 -10
- package/dist/cli.js +17 -10
- package/dist/config/config.js +80 -33
- package/dist/doctor/checks.js +10 -10
- package/dist/init.js +1 -1
- package/dist/install/flags.js +7 -2
- package/dist/install/mcp.js +14 -3
- package/dist/install/plan.js +11 -7
- package/dist/install/settings.js +17 -4
- package/dist/update/update.js +47 -20
- package/package.json +1 -1
- /package/dist/{banco → banco-dados}/ambiente.js +0 -0
- /package/dist/{banco → banco-dados}/cadastro.js +0 -0
- /package/dist/{banco → banco-dados}/drivers/index.js +0 -0
- /package/dist/{banco → banco-dados}/drivers/mariadb.js +0 -0
- /package/dist/{banco → banco-dados}/drivers/postgres.js +0 -0
- /package/dist/{banco → banco-dados}/drivers/sqlite.js +0 -0
- /package/dist/{banco → banco-dados}/drivers/tipos.js +0 -0
- /package/dist/{banco → banco-dados}/lgpd/aplicar.js +0 -0
- /package/dist/{banco → banco-dados}/lgpd/detectar.js +0 -0
- /package/dist/{banco → banco-dados}/lgpd/mascarar.js +0 -0
- /package/dist/{banco → banco-dados}/mcp.js +0 -0
- /package/dist/{banco → banco-dados}/somente-leitura.js +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fontdata",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"mcpServers": {
|
|
5
|
-
"
|
|
5
|
+
"banco_dados": { "command": "npx", "args": ["-y", "@fontdata/ai-harness@3.0.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
|
@@ -96,7 +96,7 @@ npx @fontdata/ai-harness init --comandos resolver-bug,nova-tarefa --sem-harness
|
|
|
96
96
|
| `--sem-harness` | Não inclui o comando-mestre `/harness` |
|
|
97
97
|
| `--sem-hooks` / `--sem-agents` | Não instala hooks / agents |
|
|
98
98
|
| `--sem-comentarios` | Não instala a rule de comentários, o hook e a catraca do legado |
|
|
99
|
-
| `--sem-banco` | Não registra o servidor MCP de banco no `.mcp.json` e lembra a recusa no config |
|
|
99
|
+
| `--sem-banco-dados` | Não registra o servidor MCP de banco no `.mcp.json` e lembra a recusa no config |
|
|
100
100
|
| `--claude-md criar\|manter\|backup\|sobrescrever` | O que fazer com o `CLAUDE.md` (padrão: `criar`) |
|
|
101
101
|
| `--conflito sobrescrever\|pular` | Arquivos que já existem (padrão: `pular`) |
|
|
102
102
|
| `--yes` | Aplica; sem ele, só simula |
|
|
@@ -129,26 +129,26 @@ 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 adicionar erp --projeto # pergunta os dados, testa e grava ERP_DATABASE_URL
|
|
133
|
-
npx @fontdata/ai-harness banco testar
|
|
132
|
+
npx @fontdata/ai-harness banco-dados adicionar erp --projeto # pergunta os dados, testa e grava ERP_DATABASE_URL
|
|
133
|
+
npx @fontdata/ai-harness banco-dados testar
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
O `adicionar --projeto` grava `{ "nome": "erp", "dsn_env": "ERP_DATABASE_URL" }` em `
|
|
136
|
+
O `adicionar --projeto` grava `{ "nome": "erp", "dsn_env": "ERP_DATABASE_URL" }` em `banco_dados.conexoes` e a variável
|
|
137
137
|
no `.claude/settings.local.json`. Faça commit do `config.json`; cada colega roda o mesmo comando, que grava só a
|
|
138
138
|
variável. `"producao": true` na entrada vale para a equipe toda. Quem prefere um cadastro por máquina
|
|
139
|
-
(`~/.config/ai-harness/conexoes.json`) usa `banco adicionar erp-dev` sem `--projeto` e `banco usar erp-dev` no
|
|
139
|
+
(`~/.config/ai-harness/conexoes.json`) usa `banco-dados adicionar erp-dev` sem `--projeto` e `banco-dados usar erp-dev` no
|
|
140
140
|
projeto.
|
|
141
141
|
|
|
142
|
-
O servidor MCP `
|
|
143
|
-
tudo somente leitura. Ele vem no plugin; sem plugin, o `init` e o `update` o registram no `.mcp.json`
|
|
144
|
-
pula). Conexão de produção nasce bloqueada. Use um usuário de banco só com leitura.
|
|
142
|
+
O servidor MCP `banco_dados` entrega ao Claude `listar_conexoes`, `listar_tabelas`, `descrever_tabela` e
|
|
143
|
+
`consultar`, tudo somente leitura. Ele vem no plugin; sem plugin, o `init` e o `update` o registram no `.mcp.json`
|
|
144
|
+
(`--sem-banco-dados` pula). Conexão de produção nasce bloqueada. Use um usuário de banco só com leitura.
|
|
145
145
|
|
|
146
146
|
Os dados pessoais (CPF, CNPJ, e-mail, telefone, nome, endereço e afins) saem com **mascaramento LGPD**, feito no
|
|
147
147
|
servidor antes de a resposta chegar ao Claude; os filtros rodam sobre o dado real. Por isso a `consultar` fica
|
|
148
148
|
liberada nas permissões, sem pedir aprovação. Falsos positivos e colunas extras se ajustam em
|
|
149
|
-
`
|
|
149
|
+
`banco_dados.lgpd.liberar_colunas` e `banco_dados.lgpd.mascarar_colunas`; desligar, só no cadastro da máquina. Conexões do
|
|
150
150
|
projeto, cadastro, limites do filtro, produção, mascaramento e dúvidas frequentes estão no
|
|
151
|
-
[guia de banco de dados](docs/guias/banco-
|
|
151
|
+
[guia de banco de dados](docs/guias/banco-dados.md).
|
|
152
152
|
|
|
153
153
|
## Subcomandos do CLI
|
|
154
154
|
|
|
@@ -161,8 +161,8 @@ projeto, cadastro, limites do filtro, produção, mascaramento e dúvidas freque
|
|
|
161
161
|
| `memoria [caminho]` | Índice de quem mexeu em cada arquivo (`--arquivo <caminho>`, `--regressao <id>`) |
|
|
162
162
|
| `evento <evento>` | Grava no rastro do trabalho (usado por fluxos e agents) |
|
|
163
163
|
| `conflitos <id>` | Aponta trabalhos em execução que mexem nos mesmos arquivos; `--aguardar` espera liberar (usado pelo `/executar-plan`) |
|
|
164
|
-
| `banco <subcomando>` | Conexões de banco somente leitura: `adicionar`, `listar`, `testar`, `usar`, `remover` |
|
|
165
|
-
| `banco-mcp` | Servidor MCP de leitura do banco, iniciado pelo Claude Code |
|
|
164
|
+
| `banco-dados <subcomando>` | Conexões de banco somente leitura: `adicionar`, `listar`, `testar`, `usar`, `remover` |
|
|
165
|
+
| `banco-dados-mcp` | Servidor MCP de leitura do banco, iniciado pelo Claude Code |
|
|
166
166
|
| `comentarios [caminho]` | Instala o hook do padrão de comentários com a catraca do legado (`--yes`) |
|
|
167
167
|
| `list` | Lista o catálogo |
|
|
168
168
|
|
|
@@ -2,23 +2,23 @@ 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 { CONFIG_PATH, gravarConexaoProjeto, readConfig, REGRA_DSN_ENV, REGRA_NOME_CONEXAO, validarConfigParaEscrita, writeDefaultConfig, } from '../config/config.js';
|
|
5
|
+
import { comSecaoBanco, CONFIG_PATH, gravarConexaoProjeto, readConfig, 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
8
|
import { gravarVariavelSettingsLocal, lerEnvSettingsLocal, lerVariavel, 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
|
-
'Uso: ai-harness banco <subcomando>',
|
|
12
|
+
'Uso: ai-harness banco-dados <subcomando>',
|
|
13
13
|
'',
|
|
14
14
|
' adicionar <nome> [--dsn <dsn>] [--producao] [--sem-testar] [--substituir]',
|
|
15
15
|
' cadastra a conexão nesta máquina (sem --dsn, pergunta os dados)',
|
|
16
16
|
' adicionar <nome> --projeto [--env <VAR>] [--dsn <dsn>] [--producao] [--sem-testar] [--substituir]',
|
|
17
17
|
' grava o DSN na variável <NOME>_DATABASE_URL (ou --env) do .claude/settings.local.json',
|
|
18
|
-
' e a conexão { nome, dsn_env } em
|
|
18
|
+
' e a conexão { nome, dsn_env } em banco_dados.conexoes do .ai-harness/config.json',
|
|
19
19
|
' listar mostra as conexões cadastradas e as do projeto, sem a senha',
|
|
20
20
|
' testar [nome] testa a conexão pedida ou todas as do projeto',
|
|
21
|
-
' usar <nome...> grava
|
|
21
|
+
' usar <nome...> grava banco_dados.conexoes no .ai-harness/config.json (mantém as entradas { nome, dsn_env })',
|
|
22
22
|
' remover <nome> apaga a conexão do cadastro',
|
|
23
23
|
].join(EOL);
|
|
24
24
|
const PORTA_PADRAO = { postgres: '5432', mariadb: '3306' };
|
|
@@ -121,7 +121,7 @@ async function adicionar(ctx, argv) {
|
|
|
121
121
|
const { posicionais, marcadas, valores } = lerArgumentos(argv, ['--producao', '--sem-testar', '--substituir', '--projeto'], ['--dsn', '--env']);
|
|
122
122
|
const [nome, ...sobra] = posicionais;
|
|
123
123
|
if (!nome || sobra.length > 0)
|
|
124
|
-
throw new Error('Uso: ai-harness banco adicionar <nome> [--projeto [--env <VAR>]] [--dsn <dsn>] [--producao] [--sem-testar] [--substituir]');
|
|
124
|
+
throw new Error('Uso: ai-harness banco-dados adicionar <nome> [--projeto [--env <VAR>]] [--dsn <dsn>] [--producao] [--sem-testar] [--substituir]');
|
|
125
125
|
if (marcadas.has('--projeto'))
|
|
126
126
|
return adicionarNoProjeto(ctx, nome, marcadas, valores);
|
|
127
127
|
if (valores['--env'] !== undefined)
|
|
@@ -144,25 +144,25 @@ async function adicionar(ctx, argv) {
|
|
|
144
144
|
}
|
|
145
145
|
salvarConexao(nome, conexao, { substituir, caminho: ctx.cadastro });
|
|
146
146
|
ctx.log(`Conexão ${nome} gravada em ${ctx.cadastro}: ${resumirConexao(conexao)}${conexao.producao ? ' (produção)' : ''}.`);
|
|
147
|
-
ctx.log(`No projeto, rode: npx @fontdata/ai-harness banco usar ${nome}`);
|
|
147
|
+
ctx.log(`No projeto, rode: npx @fontdata/ai-harness banco-dados usar ${nome}`);
|
|
148
148
|
return 0;
|
|
149
149
|
}
|
|
150
|
-
/** Procura o nome no JSON bruto de
|
|
150
|
+
/** Procura o nome no JSON bruto de banco_dados.conexoes; recusa lista inválida antes de qualquer gravação. */
|
|
151
151
|
function entradaDeclarada(cwd, nome) {
|
|
152
152
|
validarConfigParaEscrita(cwd);
|
|
153
153
|
const arquivo = join(cwd, CONFIG_PATH);
|
|
154
154
|
if (!existsSync(arquivo))
|
|
155
155
|
return { forma: 'ausente' };
|
|
156
156
|
const dados = JSON.parse(readFileSync(arquivo, 'utf8'));
|
|
157
|
-
const banco = isObject(dados)
|
|
157
|
+
const banco = isObject(dados) ? secaoBanco(dados) : {};
|
|
158
158
|
if (banco.conexoes === undefined)
|
|
159
159
|
return { forma: 'ausente' };
|
|
160
160
|
if (!Array.isArray(banco.conexoes))
|
|
161
|
-
throw new Error(`
|
|
161
|
+
throw new Error(`banco_dados.conexoes em ${CONFIG_PATH} não é uma lista; corrija-o antes de gravar a conexão ${nome}.`);
|
|
162
162
|
const achada = banco.conexoes.some((entrada) => (isObject(entrada) ? entrada.nome : entrada) === nome);
|
|
163
163
|
if (!achada)
|
|
164
164
|
return { forma: 'ausente' };
|
|
165
|
-
const conexao = readConfig(cwd).config.
|
|
165
|
+
const conexao = readConfig(cwd).config.banco_dados.projeto[nome];
|
|
166
166
|
return conexao ? { forma: 'objeto', conexao } : { forma: 'outra' };
|
|
167
167
|
}
|
|
168
168
|
function variavelPadrao(nome) {
|
|
@@ -183,7 +183,7 @@ async function adicionarNoProjeto(ctx, nome, marcadas, valores) {
|
|
|
183
183
|
const declarada = entradaDeclarada(ctx.cwd, nome);
|
|
184
184
|
let variavel = pedida ?? variavelPadrao(nome);
|
|
185
185
|
if (declarada.forma === 'outra' && !substituir) {
|
|
186
|
-
ctx.erro(`A conexão ${nome} já está em
|
|
186
|
+
ctx.erro(`A conexão ${nome} já está em banco_dados.conexoes de ${CONFIG_PATH} sem dsn_env. Use --substituir para trocá-la pela conexão do projeto.`);
|
|
187
187
|
return 1;
|
|
188
188
|
}
|
|
189
189
|
if (declarada.forma === 'objeto') {
|
|
@@ -234,7 +234,7 @@ async function adicionarNoProjeto(ctx, nome, marcadas, valores) {
|
|
|
234
234
|
const { changed } = gravarVariavelSettingsLocal(ctx.cwd, variavel, valor);
|
|
235
235
|
ctx.log(changed ? `Variável ${variavel} gravada em ${SETTINGS_LOCAL_PATH}.` : `Variável ${variavel} já estava com esse valor em ${SETTINGS_LOCAL_PATH}.`);
|
|
236
236
|
if (declarada.forma === 'objeto' && !promover) {
|
|
237
|
-
ctx.log(`Conexão ${nome} já declarada em
|
|
237
|
+
ctx.log(`Conexão ${nome} já declarada em banco_dados.conexoes de ${CONFIG_PATH}; a entrada não foi alterada.`);
|
|
238
238
|
return 0;
|
|
239
239
|
}
|
|
240
240
|
const entrada = declarada.forma === 'objeto'
|
|
@@ -246,7 +246,7 @@ async function adicionarNoProjeto(ctx, nome, marcadas, valores) {
|
|
|
246
246
|
...(producao ? { producao: true } : {}),
|
|
247
247
|
};
|
|
248
248
|
gravarConexaoProjeto(ctx.cwd, entrada, { substituir: declarada.forma !== 'ausente' });
|
|
249
|
-
ctx.log(`Conexão ${nome} gravada em
|
|
249
|
+
ctx.log(`Conexão ${nome} gravada em banco_dados.conexoes de ${CONFIG_PATH} (dsn_env ${variavel})${entrada.producao ? ', produção' : ''}.`);
|
|
250
250
|
if (declarada.forma === 'ausente')
|
|
251
251
|
ctx.log(`Versione o ${CONFIG_PATH}; cada colega define ${variavel} no próprio ambiente.`);
|
|
252
252
|
return 0;
|
|
@@ -257,10 +257,10 @@ function listar(ctx, argv) {
|
|
|
257
257
|
for (const problema of problemas)
|
|
258
258
|
ctx.erro(problema);
|
|
259
259
|
const { config } = readConfig(ctx.cwd);
|
|
260
|
-
const doProjeto = config.
|
|
260
|
+
const doProjeto = config.banco_dados.conexoes.flatMap((nome) => config.banco_dados.projeto[nome] ?? []);
|
|
261
261
|
const nomes = Object.keys(conexoes).sort();
|
|
262
262
|
if (nomes.length === 0 && doProjeto.length === 0) {
|
|
263
|
-
ctx.log(`Nenhuma conexão cadastrada em ${ctx.cadastro}. Cadastre com: npx @fontdata/ai-harness banco adicionar <nome>`);
|
|
263
|
+
ctx.log(`Nenhuma conexão cadastrada em ${ctx.cadastro}. Cadastre com: npx @fontdata/ai-harness banco-dados adicionar <nome>`);
|
|
264
264
|
return 0;
|
|
265
265
|
}
|
|
266
266
|
for (const nome of nomes) {
|
|
@@ -291,16 +291,16 @@ function listar(ctx, argv) {
|
|
|
291
291
|
async function testar(ctx, argv) {
|
|
292
292
|
const { posicionais } = lerArgumentos(argv, []);
|
|
293
293
|
if (posicionais.length > 1)
|
|
294
|
-
throw new Error('Uso: ai-harness banco testar [nome]');
|
|
294
|
+
throw new Error('Uso: ai-harness banco-dados testar [nome]');
|
|
295
295
|
const [nome] = posicionais;
|
|
296
296
|
const cadastro = lerCadastro(ctx.cadastro);
|
|
297
|
-
const doProjeto = nome !== undefined && nome in readConfig(ctx.cwd).config.
|
|
297
|
+
const doProjeto = nome !== undefined && nome in readConfig(ctx.cwd).config.banco_dados.projeto;
|
|
298
298
|
let alvos;
|
|
299
299
|
let ok = true;
|
|
300
300
|
if (nome !== undefined && !doProjeto) {
|
|
301
301
|
const conexao = cadastro.conexoes[nome];
|
|
302
302
|
if (!conexao) {
|
|
303
|
-
ctx.erro(`Conexão ${nome} não encontrada em ${ctx.cadastro}; rode banco adicionar ${nome}.`);
|
|
303
|
+
ctx.erro(`Conexão ${nome} não encontrada em ${ctx.cadastro}; rode banco-dados adicionar ${nome}.`);
|
|
304
304
|
return 1;
|
|
305
305
|
}
|
|
306
306
|
alvos = [{ nome, ...conexao }];
|
|
@@ -313,7 +313,7 @@ async function testar(ctx, argv) {
|
|
|
313
313
|
for (const problema of resolvidas.problemas)
|
|
314
314
|
ctx.erro(problema);
|
|
315
315
|
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 adicionar ${faltando}.`);
|
|
316
|
+
ctx.erro(`Conexão ${faltando} não encontrada nesta máquina; rode banco-dados adicionar ${faltando}.`);
|
|
317
317
|
ok = false;
|
|
318
318
|
}
|
|
319
319
|
for (const ausente of resolvidas.variaveisAusentes.filter(pedida)) {
|
|
@@ -327,7 +327,7 @@ async function testar(ctx, argv) {
|
|
|
327
327
|
}
|
|
328
328
|
alvos = resolvidas.conexoes.filter(pedida);
|
|
329
329
|
if (alvos.length === 0 && ok) {
|
|
330
|
-
ctx.erro('Nenhuma conexão de banco configurada neste projeto; rode npx @fontdata/ai-harness banco usar <nome>.');
|
|
330
|
+
ctx.erro('Nenhuma conexão de banco configurada neste projeto; rode npx @fontdata/ai-harness banco-dados usar <nome>.');
|
|
331
331
|
return 1;
|
|
332
332
|
}
|
|
333
333
|
}
|
|
@@ -346,7 +346,7 @@ async function testar(ctx, argv) {
|
|
|
346
346
|
function usar(ctx, argv) {
|
|
347
347
|
const { posicionais: nomes } = lerArgumentos(argv, []);
|
|
348
348
|
if (nomes.length === 0)
|
|
349
|
-
throw new Error('Uso: ai-harness banco usar <nome...>');
|
|
349
|
+
throw new Error('Uso: ai-harness banco-dados usar <nome...>');
|
|
350
350
|
writeDefaultConfig(ctx.cwd);
|
|
351
351
|
const arquivo = join(ctx.cwd, CONFIG_PATH);
|
|
352
352
|
let dados;
|
|
@@ -354,11 +354,11 @@ function usar(ctx, argv) {
|
|
|
354
354
|
dados = JSON.parse(readFileSync(arquivo, 'utf8'));
|
|
355
355
|
}
|
|
356
356
|
catch (erro) {
|
|
357
|
-
throw new Error(`${CONFIG_PATH} está com JSON inválido (${erro.message}); corrija antes de rodar banco usar.`);
|
|
357
|
+
throw new Error(`${CONFIG_PATH} está com JSON inválido (${erro.message}); corrija antes de rodar banco-dados usar.`);
|
|
358
358
|
}
|
|
359
359
|
if (!isObject(dados))
|
|
360
|
-
throw new Error(`${CONFIG_PATH} precisa ser um objeto JSON; corrija antes de rodar banco usar.`);
|
|
361
|
-
const banco =
|
|
360
|
+
throw new Error(`${CONFIG_PATH} precisa ser um objeto JSON; corrija antes de rodar banco-dados usar.`);
|
|
361
|
+
const banco = secaoBanco(dados);
|
|
362
362
|
const objetos = new Map();
|
|
363
363
|
for (const entrada of Array.isArray(banco.conexoes) ? banco.conexoes : []) {
|
|
364
364
|
if (isObject(entrada) && typeof entrada.nome === 'string' && !objetos.has(entrada.nome))
|
|
@@ -366,12 +366,12 @@ function usar(ctx, argv) {
|
|
|
366
366
|
}
|
|
367
367
|
const unicos = [...new Set(nomes)];
|
|
368
368
|
const proximas = unicos.map((nome) => objetos.get(nome) ?? nome);
|
|
369
|
-
writeFileSync(arquivo, `${JSON.stringify(
|
|
370
|
-
ctx.log(`
|
|
369
|
+
writeFileSync(arquivo, `${JSON.stringify(comSecaoBanco(dados, { ...banco, conexoes: proximas }), null, 2)}\n`);
|
|
370
|
+
ctx.log(`banco_dados.conexoes em ${CONFIG_PATH}: ${unicos.join(', ')}.`);
|
|
371
371
|
const { conexoes } = lerCadastro(ctx.cadastro);
|
|
372
372
|
const ausentes = unicos.filter((nome) => !(nome in conexoes) && !objetos.has(nome));
|
|
373
373
|
if (ausentes.length > 0) {
|
|
374
|
-
ctx.erro(`Aviso: sem cadastro nesta máquina para ${ausentes.join(', ')}; rode banco adicionar <nome> para cada uma.`);
|
|
374
|
+
ctx.erro(`Aviso: sem cadastro nesta máquina para ${ausentes.join(', ')}; rode banco-dados adicionar <nome> para cada uma.`);
|
|
375
375
|
}
|
|
376
376
|
return 0;
|
|
377
377
|
}
|
|
@@ -379,7 +379,7 @@ function remover(ctx, argv) {
|
|
|
379
379
|
const { posicionais } = lerArgumentos(argv, []);
|
|
380
380
|
const [nome, ...sobra] = posicionais;
|
|
381
381
|
if (!nome || sobra.length > 0)
|
|
382
|
-
throw new Error('Uso: ai-harness banco remover <nome>');
|
|
382
|
+
throw new Error('Uso: ai-harness banco-dados remover <nome>');
|
|
383
383
|
if (!removerConexao(nome, ctx.cadastro)) {
|
|
384
384
|
ctx.erro(`Conexão ${nome} não existe em ${ctx.cadastro}.`);
|
|
385
385
|
return 1;
|
|
@@ -387,7 +387,7 @@ function remover(ctx, argv) {
|
|
|
387
387
|
ctx.log(`Conexão ${nome} removida de ${ctx.cadastro}.`);
|
|
388
388
|
return 0;
|
|
389
389
|
}
|
|
390
|
-
/** `ai-harness banco <subcomando>`: cadastro de conexões da máquina e escolha das conexões do projeto. */
|
|
390
|
+
/** `ai-harness banco-dados <subcomando>`: cadastro de conexões da máquina e escolha das conexões do projeto. */
|
|
391
391
|
export async function runBancoCommand(argv, opcoes = {}) {
|
|
392
392
|
const saida = opcoes.saida ?? { log: (texto) => console.log(texto), erro: (texto) => console.error(texto) };
|
|
393
393
|
const cadastro = opcoes.caminhoCadastro ?? caminhoCadastro();
|
|
@@ -420,7 +420,7 @@ export async function runBancoCommand(argv, opcoes = {}) {
|
|
|
420
420
|
return 0;
|
|
421
421
|
default:
|
|
422
422
|
if (subcomando !== undefined)
|
|
423
|
-
ctx.erro(`Subcomando desconhecido: banco ${subcomando}`);
|
|
423
|
+
ctx.erro(`Subcomando desconhecido: banco-dados ${subcomando}`);
|
|
424
424
|
ctx.erro(AJUDA);
|
|
425
425
|
return 1;
|
|
426
426
|
}
|
|
@@ -72,7 +72,7 @@ function resolverDoProjeto(declarada, raiz, env, saida) {
|
|
|
72
72
|
saida.descartadas.push({ nome, motivo });
|
|
73
73
|
saida.problemas.push(`Conexão ${nome} descartada: ${motivo}.`);
|
|
74
74
|
}
|
|
75
|
-
/** Percorre
|
|
75
|
+
/** Percorre banco_dados.conexoes na ordem; sem a lista declarada no projeto, vale o DATABASE_URL do .env como a conexão `env`. */
|
|
76
76
|
export function resolverConexoes(raiz, opcoes = {}) {
|
|
77
77
|
const { config, problems } = readConfig(raiz);
|
|
78
78
|
const saida = {
|
|
@@ -83,7 +83,7 @@ export function resolverConexoes(raiz, opcoes = {}) {
|
|
|
83
83
|
problemas: [...problems],
|
|
84
84
|
segredos: [],
|
|
85
85
|
};
|
|
86
|
-
const { conexoes: nomes, projeto, declarado } = config.
|
|
86
|
+
const { conexoes: nomes, projeto, declarado } = config.banco_dados;
|
|
87
87
|
if (!declarado) {
|
|
88
88
|
const url = lerDatabaseUrl(raiz);
|
|
89
89
|
if (url) {
|
|
@@ -118,10 +118,10 @@ 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 usar <nome>');
|
|
121
|
+
throw new Error('Nenhuma conexão de banco configurada neste projeto; rode npx @fontdata/ai-harness banco-dados usar <nome>');
|
|
122
122
|
}
|
|
123
123
|
const escolhida = nome === undefined ? resolvidas[0] : resolvidas.find((conexao) => conexao.nome === nome);
|
|
124
124
|
if (!escolhida)
|
|
125
|
-
throw new Error(`Conexão ${nome} não está em
|
|
125
|
+
throw new Error(`Conexão ${nome} não está em banco_dados.conexoes deste projeto`);
|
|
126
126
|
return escolhida;
|
|
127
127
|
}
|
|
@@ -6,7 +6,7 @@ import { categoriaDaColuna, mascararResultado, mascararTexto } from './lgpd/apli
|
|
|
6
6
|
import { validarLeitura } from './somente-leitura.js';
|
|
7
7
|
const CONEXAO = {
|
|
8
8
|
type: 'string',
|
|
9
|
-
description: 'Nome da conexão do projeto; sem ele vale a primeira de
|
|
9
|
+
description: 'Nome da conexão do projeto; sem ele vale a primeira de banco_dados.conexoes.',
|
|
10
10
|
};
|
|
11
11
|
export const FERRAMENTAS = [
|
|
12
12
|
{
|
|
@@ -42,7 +42,7 @@ export const FERRAMENTAS = [
|
|
|
42
42
|
limite: {
|
|
43
43
|
type: 'integer',
|
|
44
44
|
minimum: 1,
|
|
45
|
-
description: 'Máximo de linhas devolvidas; nunca passa de
|
|
45
|
+
description: 'Máximo de linhas devolvidas; nunca passa de banco_dados.limite_linhas do projeto.',
|
|
46
46
|
},
|
|
47
47
|
conexao: CONEXAO,
|
|
48
48
|
},
|
|
@@ -54,17 +54,17 @@ export const FERRAMENTAS = [
|
|
|
54
54
|
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
|
-
'As conexões vêm do cadastro da máquina ou do projeto: uma conexão do projeto é declarada em
|
|
58
|
-
'Sem conexão configurada, oriente o usuário a rodar no terminal `npx @fontdata/ai-harness banco adicionar <nome>` e, no projeto, `npx @fontdata/ai-harness banco usar <nome>`.',
|
|
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 no terminal `npx @fontdata/ai-harness banco-dados adicionar <nome>` e, no projeto, `npx @fontdata/ai-harness banco-dados usar <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 usar <nome> no projeto';
|
|
65
|
+
const ORIENTACAO_USAR = 'rode npx @fontdata/ai-harness banco-dados usar <nome> no projeto';
|
|
66
66
|
function orientarAdicionar(nome) {
|
|
67
|
-
return `Conexão ${nome} não encontrada nesta máquina; rode npx @fontdata/ai-harness banco adicionar ${nome}`;
|
|
67
|
+
return `Conexão ${nome} não encontrada nesta máquina; rode npx @fontdata/ai-harness banco-dados adicionar ${nome}`;
|
|
68
68
|
}
|
|
69
69
|
function orientarVariavel(nome, variavel) {
|
|
70
70
|
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`;
|
|
@@ -125,7 +125,7 @@ function lerLimite(valor, banco) {
|
|
|
125
125
|
}
|
|
126
126
|
return Math.min(valor, banco.limite_linhas);
|
|
127
127
|
}
|
|
128
|
-
/** Sem nome, vale a primeira declarada em
|
|
128
|
+
/** Sem nome, vale a primeira declarada em banco_dados.conexoes; se ela não estiver disponível, o erro é dela, nunca outra conexão. */
|
|
129
129
|
function escolher(resolvidas, banco, nome) {
|
|
130
130
|
const alvo = nome ?? banco.conexoes[0];
|
|
131
131
|
if (alvo !== undefined) {
|
|
@@ -173,7 +173,7 @@ async function executar(nome, args, contexto, banco, resolvidas) {
|
|
|
173
173
|
if (descartadas.length > 0)
|
|
174
174
|
dados.descartadas = descartadas;
|
|
175
175
|
if (conexoes.length + faltando.length + variaveisAusentes.length + descartadas.length === 0) {
|
|
176
|
-
dados.orientacao = `Nenhuma conexão configurada; rode npx @fontdata/ai-harness banco adicionar <nome> e ${ORIENTACAO_USAR}`;
|
|
176
|
+
dados.orientacao = `Nenhuma conexão configurada; rode npx @fontdata/ai-harness banco-dados adicionar <nome> e ${ORIENTACAO_USAR}`;
|
|
177
177
|
}
|
|
178
178
|
if (resolvidas.problemas.length > 0)
|
|
179
179
|
dados.problemas = resolvidas.problemas;
|
|
@@ -183,7 +183,7 @@ async function executar(nome, args, contexto, banco, resolvidas) {
|
|
|
183
183
|
return erro(`Ferramenta desconhecida: ${nome}`);
|
|
184
184
|
const conexao = escolher(resolvidas, banco, argumentoTexto(args, 'conexao', false));
|
|
185
185
|
if (conexao.producao && banco.producao === 'bloqueio') {
|
|
186
|
-
return erro(`Conexão ${conexao.nome} é de produção e está bloqueada (
|
|
186
|
+
return erro(`Conexão ${conexao.nome} é de produção e está bloqueada (banco_dados.producao: "bloqueio"). Use uma cópia ou troque banco_dados.producao para "aviso" em .ai-harness/config.json de propósito.`);
|
|
187
187
|
}
|
|
188
188
|
let dados;
|
|
189
189
|
let rodape = '';
|
|
@@ -229,7 +229,7 @@ export async function executarFerramenta(nome, args, contexto) {
|
|
|
229
229
|
conexoes = resolvidas.conexoes;
|
|
230
230
|
segredos = resolvidas.segredos;
|
|
231
231
|
const argumentos = typeof args === 'object' && args !== null && !Array.isArray(args) ? args : {};
|
|
232
|
-
const resultado = await executar(nome, argumentos, contexto, config.
|
|
232
|
+
const resultado = await executar(nome, argumentos, contexto, config.banco_dados, resolvidas);
|
|
233
233
|
const texto = limpar(resultado.content[0].text, conexoes, segredos);
|
|
234
234
|
resultado.content[0].text = resultado.isError ? mascararTexto(texto) : texto;
|
|
235
235
|
return resultado;
|
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
import { allCommands, loadCatalog } from './catalog.js';
|
|
6
6
|
import { runCommand, runnableIds, suggestIds } from './run.js';
|
|
7
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
/** Comandos renomeados depois da 2.7.0: o nome antigo aponta o novo em vez de cair em "Comando desconhecido". */
|
|
9
|
+
const RENAMED_COMMANDS = { banco: 'banco-dados', 'banco-mcp': 'banco-dados-mcp' };
|
|
8
10
|
const RESERVED = new Set([
|
|
9
11
|
'init',
|
|
10
12
|
'doctor',
|
|
@@ -14,8 +16,8 @@ const RESERVED = new Set([
|
|
|
14
16
|
'conflitos',
|
|
15
17
|
'comentarios',
|
|
16
18
|
'memoria',
|
|
17
|
-
'banco',
|
|
18
|
-
'banco-mcp',
|
|
19
|
+
'banco-dados',
|
|
20
|
+
'banco-dados-mcp',
|
|
19
21
|
'list',
|
|
20
22
|
'run',
|
|
21
23
|
'help',
|
|
@@ -49,15 +51,15 @@ Gerenciar:
|
|
|
49
51
|
init [caminho] Instala os slash commands em .claude/commands do projeto (interativo)
|
|
50
52
|
doctor [caminho] Diagnostica a instalação (arquivos, lock, skills, agents, hooks, Node)
|
|
51
53
|
update [caminho] Atualiza o que foi instalado: --check só lista, --yes aplica, --forcar sobrescreve conflitos
|
|
52
|
-
--sem-banco: não registra o MCP de banco no .mcp.json e lembra a recusa no config
|
|
54
|
+
--sem-banco-dados: não registra o MCP de banco no .mcp.json e lembra a recusa no config
|
|
53
55
|
migrar [caminho] Move docs/specs, plans, bugs e investigacoes para docs/trabalhos/<id>/ (--yes aplica)
|
|
54
56
|
--reclassificar: planos já migrados em em-execucao viram concluido ou legado
|
|
55
57
|
comentarios [caminho] Instala o hook de comentários e a catraca do legado (--yes aplica)
|
|
56
58
|
memoria [caminho] Índice de quem mexeu em cada arquivo (--arquivo <caminho>, --regressao <id do bug>)
|
|
57
59
|
evento <evento> Grava no rastro do trabalho (--trabalho, --task, --nome, --resultado, --dados '{json}')
|
|
58
60
|
conflitos <id> Aponta trabalhos em execução que mexem nos mesmos arquivos (--aguardar espera liberar)
|
|
59
|
-
banco <subcomando> Conexões de banco somente leitura: adicionar, listar, testar, usar, remover
|
|
60
|
-
banco-mcp
|
|
61
|
+
banco-dados <subcomando> Conexões de banco somente leitura: adicionar, listar, testar, usar, remover
|
|
62
|
+
banco-dados-mcp Servidor MCP de leitura do banco, usado pelo Claude Code (stdio)
|
|
61
63
|
list Lista todos os comandos disponíveis no catálogo
|
|
62
64
|
|
|
63
65
|
Init sem perguntas (CI):
|
|
@@ -66,7 +68,7 @@ Init sem perguntas (CI):
|
|
|
66
68
|
--sem-hooks não instala os hooks do harness
|
|
67
69
|
--sem-agents não instala os agents somente leitura
|
|
68
70
|
--sem-comentarios não instala a rule, o hook e a catraca de comentários
|
|
69
|
-
--sem-banco
|
|
71
|
+
--sem-banco-dados não registra o servidor MCP de banco no .mcp.json
|
|
70
72
|
--claude-md criar|manter|backup|sobrescrever (padrão: criar)
|
|
71
73
|
--conflito sobrescrever|pular (padrão: pular)
|
|
72
74
|
--yes aplica; sem ele o init só simula
|
|
@@ -152,12 +154,12 @@ async function main() {
|
|
|
152
154
|
const { runConflictsCommand } = await import('./trabalhos/conflitos.js');
|
|
153
155
|
process.exit(await runConflictsCommand(argv.slice(1)));
|
|
154
156
|
}
|
|
155
|
-
if (cmd === 'banco') {
|
|
156
|
-
const { runBancoCommand } = await import('./banco/command.js');
|
|
157
|
+
if (cmd === 'banco-dados') {
|
|
158
|
+
const { runBancoCommand } = await import('./banco-dados/command.js');
|
|
157
159
|
process.exit(await runBancoCommand(argv.slice(1)));
|
|
158
160
|
}
|
|
159
|
-
if (cmd === 'banco-mcp') {
|
|
160
|
-
const { iniciarServidorMcp } = await import('./banco/mcp.js');
|
|
161
|
+
if (cmd === 'banco-dados-mcp') {
|
|
162
|
+
const { iniciarServidorMcp } = await import('./banco-dados/mcp.js');
|
|
161
163
|
await iniciarServidorMcp({ entrada: process.stdin, saida: process.stdout });
|
|
162
164
|
return;
|
|
163
165
|
}
|
|
@@ -180,6 +182,11 @@ async function main() {
|
|
|
180
182
|
const { args, claudeFlags } = splitArgs(argv.slice(1));
|
|
181
183
|
process.exit(runCommand(cmd, args, claudeFlags));
|
|
182
184
|
}
|
|
185
|
+
const renamed = RENAMED_COMMANDS[cmd];
|
|
186
|
+
if (renamed) {
|
|
187
|
+
console.error(`O comando ${cmd} mudou de nome: use ${renamed}.`);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
183
190
|
if (!RESERVED.has(cmd)) {
|
|
184
191
|
const near = suggestIds(cmd.replace(/^\//, ''), ids);
|
|
185
192
|
console.error(`Comando desconhecido: ${cmd}`);
|
package/dist/config/config.js
CHANGED
|
@@ -16,13 +16,53 @@ export const TIPOS_CONEXAO_PROJETO = ['postgres', 'mariadb', 'sqlite'];
|
|
|
16
16
|
function isObject(value) {
|
|
17
17
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
18
18
|
}
|
|
19
|
+
/** Seção do banco no config; até a 2.7.0 ela se chamava `banco`. */
|
|
20
|
+
export const BANCO_CHAVE = 'banco_dados';
|
|
21
|
+
export const BANCO_CHAVE_LEGADA = 'banco';
|
|
22
|
+
/** Só a seção com o nome antigo existe: ela vale e o update a renomeia. */
|
|
23
|
+
function soSecaoLegada(raw) {
|
|
24
|
+
return !(BANCO_CHAVE in raw) && BANCO_CHAVE_LEGADA in raw;
|
|
25
|
+
}
|
|
26
|
+
/** A seção do banco no JSON bruto: `banco_dados`, ou a `banco` antiga quando só ela existe. */
|
|
27
|
+
export function secaoBanco(raw) {
|
|
28
|
+
const secao = soSecaoLegada(raw) ? raw[BANCO_CHAVE_LEGADA] : raw[BANCO_CHAVE];
|
|
29
|
+
return isObject(secao) ? secao : {};
|
|
30
|
+
}
|
|
31
|
+
/** O JSON com a seção do banco gravada como `banco_dados`; a `banco` antiga é trocada na mesma posição. */
|
|
32
|
+
export function comSecaoBanco(raw, secao) {
|
|
33
|
+
if (!soSecaoLegada(raw))
|
|
34
|
+
return { ...raw, [BANCO_CHAVE]: secao };
|
|
35
|
+
return Object.fromEntries(Object.entries(raw).map(([chave, valor]) => (chave === BANCO_CHAVE_LEGADA ? [BANCO_CHAVE, secao] : [chave, valor])));
|
|
36
|
+
}
|
|
37
|
+
function lerJsonBruto(root) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(readFileSync(join(root, CONFIG_PATH), 'utf8'));
|
|
40
|
+
return isObject(parsed) ? parsed : undefined;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** O config ainda tem a seção do banco com o nome antigo (`banco`), que o update renomeia. */
|
|
47
|
+
export function temSecaoBancoLegada(root) {
|
|
48
|
+
const raw = lerJsonBruto(root);
|
|
49
|
+
return raw !== undefined && soSecaoLegada(raw);
|
|
50
|
+
}
|
|
51
|
+
/** Renomeia a seção `banco` para `banco_dados`, preservando o resto do JSON; devolve se mudou. */
|
|
52
|
+
export function migrarSecaoBanco(root) {
|
|
53
|
+
const raw = lerJsonBruto(root);
|
|
54
|
+
if (!raw || !soSecaoLegada(raw))
|
|
55
|
+
return false;
|
|
56
|
+
writeFileSync(join(root, CONFIG_PATH), `${JSON.stringify(comSecaoBanco(raw, secaoBanco(raw)), null, 2)}\n`);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
19
59
|
export function defaultConfig() {
|
|
20
60
|
return {
|
|
21
61
|
config_versao: CONFIG_VERSION,
|
|
22
62
|
hooks: { ...DEFAULT_HOOK_MODES },
|
|
23
63
|
rastro: { versionar: false },
|
|
24
64
|
bloqueios: { dias_para_aviso: 7 },
|
|
25
|
-
|
|
65
|
+
banco_dados: {
|
|
26
66
|
conexoes: [],
|
|
27
67
|
producao: 'bloqueio',
|
|
28
68
|
limite_linhas: 200,
|
|
@@ -41,23 +81,23 @@ const SO_NO_CADASTRO = 'O mascaramento LGPD só pode ser desligado no cadastro d
|
|
|
41
81
|
const REGRA_DE_COLUNA = /^[^.\s]+(\.[^.\s]+){0,2}$/;
|
|
42
82
|
function lerLgpd(raw, lgpd, problems) {
|
|
43
83
|
if (!isObject(raw)) {
|
|
44
|
-
problems.push(`
|
|
84
|
+
problems.push(`banco_dados.lgpd deve ser um objeto com mascarar_colunas e liberar_colunas em ${CONFIG_PATH}.`);
|
|
45
85
|
return;
|
|
46
86
|
}
|
|
47
87
|
if ('desligar' in raw)
|
|
48
|
-
problems.push(`${SO_NO_CADASTRO};
|
|
88
|
+
problems.push(`${SO_NO_CADASTRO}; banco_dados.lgpd.desligar em ${CONFIG_PATH} foi ignorado.`);
|
|
49
89
|
for (const chave of ['mascarar_colunas', 'liberar_colunas']) {
|
|
50
90
|
if (!(chave in raw))
|
|
51
91
|
continue;
|
|
52
92
|
const lista = raw[chave];
|
|
53
93
|
if (!Array.isArray(lista)) {
|
|
54
|
-
problems.push(`
|
|
94
|
+
problems.push(`banco_dados.lgpd.${chave} deve ser uma lista de colunas em ${CONFIG_PATH}.`);
|
|
55
95
|
continue;
|
|
56
96
|
}
|
|
57
97
|
lgpd[chave] = lista.filter((regra) => {
|
|
58
98
|
if (typeof regra === 'string' && REGRA_DE_COLUNA.test(regra))
|
|
59
99
|
return true;
|
|
60
|
-
problems.push(`Entrada ${JSON.stringify(regra)} ignorada em
|
|
100
|
+
problems.push(`Entrada ${JSON.stringify(regra)} ignorada em banco_dados.lgpd.${chave} de ${CONFIG_PATH}; use coluna, tabela.coluna ou schema.tabela.coluna.`);
|
|
61
101
|
return false;
|
|
62
102
|
});
|
|
63
103
|
}
|
|
@@ -83,7 +123,7 @@ function motivoConexaoInvalida(entrada) {
|
|
|
83
123
|
function lerConexoes(conexoes, banco, problems) {
|
|
84
124
|
banco.declarado = !(Array.isArray(conexoes) && conexoes.length === 0);
|
|
85
125
|
if (!Array.isArray(conexoes)) {
|
|
86
|
-
problems.push(`
|
|
126
|
+
problems.push(`banco_dados.conexoes deve ser uma lista de nomes ou de objetos { nome, dsn_env } em ${CONFIG_PATH}.`);
|
|
87
127
|
return;
|
|
88
128
|
}
|
|
89
129
|
for (const entrada of conexoes) {
|
|
@@ -94,17 +134,17 @@ function lerConexoes(conexoes, banco, problems) {
|
|
|
94
134
|
else if (isObject(entrada)) {
|
|
95
135
|
const motivo = motivoConexaoInvalida(entrada);
|
|
96
136
|
if (motivo) {
|
|
97
|
-
problems.push(`Entrada ${citada} descartada em
|
|
137
|
+
problems.push(`Entrada ${citada} descartada em banco_dados.conexoes de ${CONFIG_PATH}: ${motivo}.`);
|
|
98
138
|
continue;
|
|
99
139
|
}
|
|
100
140
|
nome = entrada.nome;
|
|
101
141
|
}
|
|
102
142
|
else {
|
|
103
|
-
problems.push(`Entrada ${citada} descartada em
|
|
143
|
+
problems.push(`Entrada ${citada} descartada em banco_dados.conexoes de ${CONFIG_PATH}; use um nome (texto não vazio) ou um objeto { nome, dsn_env }.`);
|
|
104
144
|
continue;
|
|
105
145
|
}
|
|
106
146
|
if (banco.conexoes.includes(nome)) {
|
|
107
|
-
problems.push(`Entrada ${citada} descartada em
|
|
147
|
+
problems.push(`Entrada ${citada} descartada em banco_dados.conexoes de ${CONFIG_PATH}: o nome "${nome}" já foi declarado antes.`);
|
|
108
148
|
continue;
|
|
109
149
|
}
|
|
110
150
|
banco.conexoes.push(nome);
|
|
@@ -112,8 +152,8 @@ function lerConexoes(conexoes, banco, problems) {
|
|
|
112
152
|
continue;
|
|
113
153
|
for (const chave of Object.keys(entrada).filter((chave) => !CHAVES_CONEXAO_PROJETO.includes(chave))) {
|
|
114
154
|
problems.push(chave === 'mascaramento'
|
|
115
|
-
? `${SO_NO_CADASTRO}; mascaramento da entrada ${citada} em
|
|
116
|
-
: `Chave desconhecida ${chave} ignorada na entrada ${citada} de
|
|
155
|
+
? `${SO_NO_CADASTRO}; mascaramento da entrada ${citada} em banco_dados.conexoes de ${CONFIG_PATH} foi ignorado.`
|
|
156
|
+
: `Chave desconhecida ${chave} ignorada na entrada ${citada} de banco_dados.conexoes em ${CONFIG_PATH}.`);
|
|
117
157
|
}
|
|
118
158
|
banco.projeto[nome] = {
|
|
119
159
|
nome,
|
|
@@ -130,28 +170,28 @@ function lerBanco(raw, banco, problems) {
|
|
|
130
170
|
if (MODOS_PRODUCAO.includes(raw.producao))
|
|
131
171
|
banco.producao = raw.producao;
|
|
132
172
|
else
|
|
133
|
-
problems.push(`Modo "${String(raw.producao)}" inválido para
|
|
173
|
+
problems.push(`Modo "${String(raw.producao)}" inválido para banco_dados.producao em ${CONFIG_PATH}; use aviso ou bloqueio.`);
|
|
134
174
|
}
|
|
135
175
|
if ('limite_linhas' in raw) {
|
|
136
176
|
if (inteiroEntre(raw.limite_linhas, 1, 5000))
|
|
137
177
|
banco.limite_linhas = raw.limite_linhas;
|
|
138
178
|
else
|
|
139
|
-
problems.push(`
|
|
179
|
+
problems.push(`banco_dados.limite_linhas deve ser um inteiro de 1 a 5000 em ${CONFIG_PATH}.`);
|
|
140
180
|
}
|
|
141
181
|
if ('timeout_segundos' in raw) {
|
|
142
182
|
if (inteiroEntre(raw.timeout_segundos, 1, 600))
|
|
143
183
|
banco.timeout_segundos = raw.timeout_segundos;
|
|
144
184
|
else
|
|
145
|
-
problems.push(`
|
|
185
|
+
problems.push(`banco_dados.timeout_segundos deve ser um inteiro de 1 a 600 em ${CONFIG_PATH}.`);
|
|
146
186
|
}
|
|
147
187
|
if ('registrar_mcp' in raw) {
|
|
148
188
|
if (typeof raw.registrar_mcp === 'boolean')
|
|
149
189
|
banco.registrar_mcp = raw.registrar_mcp;
|
|
150
190
|
else
|
|
151
|
-
problems.push(`
|
|
191
|
+
problems.push(`banco_dados.registrar_mcp deve ser true ou false em ${CONFIG_PATH}.`);
|
|
152
192
|
}
|
|
153
193
|
if ('mascaramento' in raw)
|
|
154
|
-
problems.push(`${SO_NO_CADASTRO};
|
|
194
|
+
problems.push(`${SO_NO_CADASTRO}; banco_dados.mascaramento em ${CONFIG_PATH} foi ignorado.`);
|
|
155
195
|
if ('lgpd' in raw)
|
|
156
196
|
lerLgpd(raw.lgpd, banco.lgpd, problems);
|
|
157
197
|
}
|
|
@@ -192,17 +232,24 @@ export function readConfig(root) {
|
|
|
192
232
|
else
|
|
193
233
|
problems.push(`bloqueios.dias_para_aviso deve ser um inteiro positivo em ${CONFIG_PATH}.`);
|
|
194
234
|
}
|
|
195
|
-
|
|
196
|
-
|
|
235
|
+
const secao = soSecaoLegada(raw) ? raw[BANCO_CHAVE_LEGADA] : raw[BANCO_CHAVE];
|
|
236
|
+
if (isObject(secao))
|
|
237
|
+
lerBanco(secao, config.banco_dados, problems);
|
|
238
|
+
if (soSecaoLegada(raw)) {
|
|
239
|
+
problems.push(`A seção banco de ${CONFIG_PATH} agora se chama banco_dados; rode npx @fontdata/ai-harness update --yes para renomeá-la.`);
|
|
240
|
+
}
|
|
241
|
+
else if (BANCO_CHAVE_LEGADA in raw) {
|
|
242
|
+
problems.push(`A seção banco de ${CONFIG_PATH} foi ignorada: vale a banco_dados. Apague a banco.`);
|
|
243
|
+
}
|
|
197
244
|
return { config, problems };
|
|
198
245
|
}
|
|
199
|
-
/** Cria a config com os padrões, sem a seção
|
|
246
|
+
/** Cria a config com os padrões, sem a seção banco_dados; retorna false se já existia (nunca sobrescreve). */
|
|
200
247
|
export function writeDefaultConfig(root) {
|
|
201
248
|
const file = join(root, CONFIG_PATH);
|
|
202
249
|
if (existsSync(file))
|
|
203
250
|
return false;
|
|
204
251
|
mkdirSync(dirname(file), { recursive: true });
|
|
205
|
-
const {
|
|
252
|
+
const { banco_dados: _banco, ...gravada } = defaultConfig();
|
|
206
253
|
writeFileSync(file, `${JSON.stringify(gravada, null, 2)}\n`);
|
|
207
254
|
return true;
|
|
208
255
|
}
|
|
@@ -222,10 +269,10 @@ function lerJsonParaEscrita(file, chave) {
|
|
|
222
269
|
export function validarConfigParaEscrita(root) {
|
|
223
270
|
const file = join(root, CONFIG_PATH);
|
|
224
271
|
if (existsSync(file))
|
|
225
|
-
lerJsonParaEscrita(file, '
|
|
272
|
+
lerJsonParaEscrita(file, 'banco_dados.conexoes');
|
|
226
273
|
}
|
|
227
274
|
/**
|
|
228
|
-
* Grava a conexão em `
|
|
275
|
+
* Grava a conexão em `banco_dados.conexoes`, preservando o resto do JSON; sem config, cria uma com os padrões.
|
|
229
276
|
* Se o nome já existir (objeto ou string), lança, ou troca a entrada na mesma posição com `substituir`.
|
|
230
277
|
* JSON inválido é recusado sem tocar no arquivo.
|
|
231
278
|
*/
|
|
@@ -234,14 +281,14 @@ export function gravarConexaoProjeto(root, conexao, { substituir = false } = {})
|
|
|
234
281
|
if (motivo)
|
|
235
282
|
throw new Error(`Conexão ${JSON.stringify(conexao.nome)} inválida: ${motivo}.`);
|
|
236
283
|
const file = join(root, CONFIG_PATH);
|
|
237
|
-
const raw = existsSync(file) ? lerJsonParaEscrita(file, '
|
|
238
|
-
const banco = raw
|
|
284
|
+
const raw = existsSync(file) ? lerJsonParaEscrita(file, 'banco_dados.conexoes') : undefined;
|
|
285
|
+
const banco = raw ? secaoBanco(raw) : {};
|
|
239
286
|
const conexoes = banco.conexoes === undefined ? [] : banco.conexoes;
|
|
240
287
|
if (!Array.isArray(conexoes))
|
|
241
|
-
throw new Error(`
|
|
288
|
+
throw new Error(`banco_dados.conexoes em ${CONFIG_PATH} não é uma lista; corrija-o antes de gravar a conexão ${conexao.nome}.`);
|
|
242
289
|
const posicao = conexoes.findIndex((entrada) => (isObject(entrada) ? entrada.nome : entrada) === conexao.nome);
|
|
243
290
|
if (posicao !== -1 && !substituir)
|
|
244
|
-
throw new Error(`A conexão ${conexao.nome} já está em
|
|
291
|
+
throw new Error(`A conexão ${conexao.nome} já está em banco_dados.conexoes de ${CONFIG_PATH}.`);
|
|
245
292
|
const entrada = {
|
|
246
293
|
nome: conexao.nome,
|
|
247
294
|
dsn_env: conexao.dsn_env,
|
|
@@ -251,28 +298,28 @@ export function gravarConexaoProjeto(root, conexao, { substituir = false } = {})
|
|
|
251
298
|
const proximas = posicao === -1 ? [...conexoes, entrada] : conexoes.map((atual, i) => (i === posicao ? entrada : atual));
|
|
252
299
|
if (!raw)
|
|
253
300
|
writeDefaultConfig(root);
|
|
254
|
-
const base = raw ?? lerJsonParaEscrita(file, '
|
|
255
|
-
writeFileSync(file, `${JSON.stringify(
|
|
301
|
+
const base = raw ?? lerJsonParaEscrita(file, 'banco_dados.conexoes');
|
|
302
|
+
writeFileSync(file, `${JSON.stringify(comSecaoBanco(base, { ...banco, conexoes: proximas }), null, 2)}\n`);
|
|
256
303
|
}
|
|
257
304
|
/**
|
|
258
|
-
* Grava só `
|
|
305
|
+
* Grava só `banco_dados.registrar_mcp`, preservando o resto do JSON; sem config, cria uma com os padrões
|
|
259
306
|
* e essa chave. JSON inválido é recusado sem tocar no arquivo.
|
|
260
307
|
*/
|
|
261
308
|
export function gravarRegistrarMcp(root, valor) {
|
|
262
309
|
const file = join(root, CONFIG_PATH);
|
|
263
310
|
let raw;
|
|
264
311
|
if (existsSync(file)) {
|
|
265
|
-
raw = lerJsonParaEscrita(file, '
|
|
312
|
+
raw = lerJsonParaEscrita(file, 'banco_dados.registrar_mcp');
|
|
266
313
|
}
|
|
267
314
|
else {
|
|
268
|
-
const {
|
|
315
|
+
const { banco_dados: _banco, ...padrao } = defaultConfig();
|
|
269
316
|
raw = padrao;
|
|
270
317
|
}
|
|
271
|
-
const banco =
|
|
318
|
+
const banco = secaoBanco(raw);
|
|
272
319
|
if (banco.registrar_mcp === valor)
|
|
273
320
|
return;
|
|
274
321
|
mkdirSync(dirname(file), { recursive: true });
|
|
275
|
-
writeFileSync(file, `${JSON.stringify(
|
|
322
|
+
writeFileSync(file, `${JSON.stringify(comSecaoBanco(raw, { ...banco, registrar_mcp: valor }), null, 2)}\n`);
|
|
276
323
|
}
|
|
277
324
|
const BLOCK_START = '# ai-harness (gerenciado pelo harness; não edite entre estas linhas)';
|
|
278
325
|
const BLOCK_END = '# /ai-harness';
|
package/dist/doctor/checks.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
2
2
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { lerEnvSettingsLocal, lerVariavel, SETTINGS_LOCAL_PATH } from '../banco/ambiente.js';
|
|
5
|
-
import { caminhoCadastro, lerCadastro } from '../banco/cadastro.js';
|
|
4
|
+
import { lerEnvSettingsLocal, lerVariavel, SETTINGS_LOCAL_PATH } from '../banco-dados/ambiente.js';
|
|
5
|
+
import { caminhoCadastro, lerCadastro } from '../banco-dados/cadastro.js';
|
|
6
6
|
import { allCommands, loadCatalog } from '../catalog.js';
|
|
7
7
|
import { CONFIG_PATH, readConfig } from '../config/config.js';
|
|
8
8
|
import { readArtifact, validateAllWorks, WORKS_DIR } from '../contratos/artefatos.js';
|
|
@@ -414,7 +414,7 @@ function settingsLocalIgnorado(root, env) {
|
|
|
414
414
|
.some((linha) => LINHAS_IGNORE_SETTINGS_LOCAL.has(linha));
|
|
415
415
|
}
|
|
416
416
|
function checkBancoProjeto(root, config, env) {
|
|
417
|
-
const conexoes = config.
|
|
417
|
+
const conexoes = config.banco_dados.conexoes.flatMap((nome) => config.banco_dados.projeto[nome] ?? []);
|
|
418
418
|
if (!conexoes.length)
|
|
419
419
|
return [];
|
|
420
420
|
const findings = [];
|
|
@@ -424,8 +424,8 @@ function checkBancoProjeto(root, config, env) {
|
|
|
424
424
|
findings.push({
|
|
425
425
|
id: 'banco-variavel-ausente',
|
|
426
426
|
severidade: 'aviso',
|
|
427
|
-
mensagem: `A conexão ${nome}, declarada em
|
|
428
|
-
correcao: `Defina ${dsn_env} com \`npx @fontdata/ai-harness banco adicionar ${nome} --projeto\` ou no ambiente do terminal.`,
|
|
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} --projeto\` ou no ambiente do terminal.`,
|
|
429
429
|
});
|
|
430
430
|
}
|
|
431
431
|
const envLocal = lerEnvSettingsLocal(root);
|
|
@@ -453,12 +453,12 @@ function checkBancoProjeto(root, config, env) {
|
|
|
453
453
|
}
|
|
454
454
|
return findings;
|
|
455
455
|
}
|
|
456
|
-
/** Confere o cadastro desta máquina e as variáveis das conexões do projeto contra `
|
|
456
|
+
/** Confere o cadastro desta máquina e as variáveis das conexões do projeto contra `banco_dados.conexoes`, sem abrir conexão. */
|
|
457
457
|
export function checkBanco(root, config, env = process.env) {
|
|
458
|
-
if (!config.
|
|
458
|
+
if (!config.banco_dados.conexoes.length)
|
|
459
459
|
return [];
|
|
460
460
|
const findings = checkBancoProjeto(root, config, env);
|
|
461
|
-
const doCadastro = config.
|
|
461
|
+
const doCadastro = config.banco_dados.conexoes.filter((nome) => !(nome in config.banco_dados.projeto));
|
|
462
462
|
if (!doCadastro.length)
|
|
463
463
|
return findings;
|
|
464
464
|
const caminho = caminhoCadastro(env);
|
|
@@ -486,8 +486,8 @@ export function checkBanco(root, config, env = process.env) {
|
|
|
486
486
|
findings.push({
|
|
487
487
|
id: 'banco-conexao-ausente',
|
|
488
488
|
severidade: 'aviso',
|
|
489
|
-
mensagem: `A conexão ${nome}, citada em
|
|
490
|
-
correcao: `Cadastre-a com \`npx @fontdata/ai-harness banco adicionar ${nome}\`.`,
|
|
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}\`.`,
|
|
491
491
|
});
|
|
492
492
|
}
|
|
493
493
|
for (const nome of doCadastro.filter((nome) => conexoes[nome]?.mascaramento === false)) {
|
package/dist/init.js
CHANGED
|
@@ -14,7 +14,7 @@ const PLUGIN_HOOKS_NOTE = 'O plugin fontdata@ai-harness está habilitado: os hoo
|
|
|
14
14
|
const PLUGIN_MCP_NOTE = 'O plugin fontdata@ai-harness está habilitado: o servidor MCP de banco já roda por ele e não foi registrado no .mcp.json, para não duplicar.';
|
|
15
15
|
/** Registra o MCP de banco no .mcp.json quando não foi recusado (flag, wizard ou config) e o plugin não o fornece. */
|
|
16
16
|
export function decidirRegistroMcp(options, root, home = homedir()) {
|
|
17
|
-
return options.mcpBanco && readConfig(root).config.
|
|
17
|
+
return options.mcpBanco && readConfig(root).config.banco_dados.registrar_mcp && !isHarnessPluginEnabled(root, home);
|
|
18
18
|
}
|
|
19
19
|
function applyMcpDecision(plan, options, root) {
|
|
20
20
|
plan.registerMcp = decidirRegistroMcp(options, root);
|
package/dist/install/flags.js
CHANGED
|
@@ -7,7 +7,7 @@ const CLAUDE_MD = {
|
|
|
7
7
|
};
|
|
8
8
|
const CONFLICTS = { sobrescrever: 'overwrite', pular: 'skip' };
|
|
9
9
|
const VALUE_FLAGS = ['--escopo', '--comandos', '--claude-md', '--conflito'];
|
|
10
|
-
const BOOLEAN_FLAGS = ['--sem-harness', '--sem-hooks', '--sem-agents', '--sem-comentarios', '--sem-banco', '--yes', '-y', '--sim'];
|
|
10
|
+
const BOOLEAN_FLAGS = ['--sem-harness', '--sem-hooks', '--sem-agents', '--sem-comentarios', '--sem-banco-dados', '--yes', '-y', '--sim'];
|
|
11
11
|
function pick(flag, value, options) {
|
|
12
12
|
const chosen = options[value];
|
|
13
13
|
if (chosen === undefined) {
|
|
@@ -28,8 +28,13 @@ function distance(a, b) {
|
|
|
28
28
|
}
|
|
29
29
|
return row[b.length] ?? 0;
|
|
30
30
|
}
|
|
31
|
+
/** Flags que mudaram de nome: o erro aponta a nova, que a distância de edição não alcança. */
|
|
32
|
+
const RENAMED_FLAGS = { '--sem-banco': '--sem-banco-dados' };
|
|
31
33
|
/** Flag conhecida mais parecida com a digitada, para sugerir em mensagens de erro. */
|
|
32
34
|
export function suggestFlag(token, flags) {
|
|
35
|
+
const renamed = RENAMED_FLAGS[token];
|
|
36
|
+
if (renamed && flags.includes(renamed))
|
|
37
|
+
return renamed;
|
|
33
38
|
const [best] = flags
|
|
34
39
|
.map((flag) => ({ flag, d: distance(token, flag) }))
|
|
35
40
|
.filter(({ d }) => d <= 2)
|
|
@@ -69,7 +74,7 @@ export function parseInitArgs(argv) {
|
|
|
69
74
|
args.agents = false;
|
|
70
75
|
else if (token === '--sem-comentarios')
|
|
71
76
|
args.comments = false;
|
|
72
|
-
else if (token === '--sem-banco')
|
|
77
|
+
else if (token === '--sem-banco-dados')
|
|
73
78
|
args.mcpBanco = false;
|
|
74
79
|
else
|
|
75
80
|
args.yes = true;
|
package/dist/install/mcp.js
CHANGED
|
@@ -2,13 +2,17 @@ import { copyFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { timestamp } from './settings.js';
|
|
4
4
|
export const MCP_PATH = '.mcp.json';
|
|
5
|
+
/** Nome do servidor de banco no `.mcp.json`; as ferramentas ficam `mcp__banco_dados__*`. */
|
|
6
|
+
export const BANCO_MCP_NOME = 'banco_dados';
|
|
7
|
+
/** Nome do servidor até a 2.7.0; a entrada gerenciada com ele é trocada pela nova no update. */
|
|
8
|
+
export const BANCO_MCP_NOME_LEGADO = 'banco';
|
|
5
9
|
const PIN_PREFIX = '@fontdata/ai-harness@';
|
|
6
10
|
function isPlainObject(value) {
|
|
7
11
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
12
|
}
|
|
9
13
|
/** Servidor MCP de banco com a versão do pacote fixada no npx, no mesmo formato do plugin. */
|
|
10
14
|
export function bancoMcpServer(versao) {
|
|
11
|
-
return { command: 'npx', args: ['-y', `${PIN_PREFIX}${versao}`, 'banco-mcp'] };
|
|
15
|
+
return { command: 'npx', args: ['-y', `${PIN_PREFIX}${versao}`, 'banco-dados-mcp'] };
|
|
12
16
|
}
|
|
13
17
|
function pinOf(entrada) {
|
|
14
18
|
if (!isPlainObject(entrada) || !Array.isArray(entrada.args))
|
|
@@ -22,9 +26,10 @@ export function ehEntradaGerenciada(entrada) {
|
|
|
22
26
|
/**
|
|
23
27
|
* Acrescenta os servidores ausentes em `mcpServers` do `.mcp.json`, preservando o resto. Com
|
|
24
28
|
* `atualizarGerenciada`, regrava também a entrada gerenciada que fixa outra versão; entrada
|
|
25
|
-
* personalizada nunca é tocada.
|
|
29
|
+
* personalizada nunca é tocada. Os nomes de `removerGerenciadas` saem só quando a entrada é gerenciada. Backup
|
|
30
|
+
* datado só quando muda; JSON inválido é recusado sem tocar.
|
|
26
31
|
*/
|
|
27
|
-
export function mergeMcpServers(root, servidores, now = new Date(), { atualizarGerenciada = false } = {}) {
|
|
32
|
+
export function mergeMcpServers(root, servidores, now = new Date(), { atualizarGerenciada = false, removerGerenciadas = [] } = {}) {
|
|
28
33
|
const file = join(root, MCP_PATH);
|
|
29
34
|
const original = existsSync(file) ? readFileSync(file, 'utf8') : null;
|
|
30
35
|
let config = {};
|
|
@@ -43,6 +48,12 @@ export function mergeMcpServers(root, servidores, now = new Date(), { atualizarG
|
|
|
43
48
|
}
|
|
44
49
|
const mcpServers = { ...(config.mcpServers ?? {}) };
|
|
45
50
|
let changed = false;
|
|
51
|
+
for (const nome of removerGerenciadas) {
|
|
52
|
+
if (!ehEntradaGerenciada(mcpServers[nome]))
|
|
53
|
+
continue;
|
|
54
|
+
delete mcpServers[nome];
|
|
55
|
+
changed = true;
|
|
56
|
+
}
|
|
46
57
|
for (const [nome, servidor] of Object.entries(servidores)) {
|
|
47
58
|
const atual = mcpServers[nome];
|
|
48
59
|
if (atual === undefined) {
|
package/dist/install/plan.js
CHANGED
|
@@ -6,10 +6,10 @@ import { applyAtomically } from './atomic.js';
|
|
|
6
6
|
import { buildLockItem, LOCK_VERSION, readLock, writeLock } from './lock.js';
|
|
7
7
|
import { HOOKS_ITEM_ID, PROJECT_HOOKS_DIR, projectHooksConfig } from './hooks.js';
|
|
8
8
|
import { neededSkillsFor, resolveSelection } from './selection.js';
|
|
9
|
-
import { bancoMcpServer, MCP_PATH, mergeMcpServers } from './mcp.js';
|
|
9
|
+
import { BANCO_MCP_NOME, bancoMcpServer, MCP_PATH, mergeMcpServers } from './mcp.js';
|
|
10
10
|
import { gravarRegistrarMcp } from '../config/config.js';
|
|
11
11
|
import { COMMENTS_RULE_PATH, commentsHooksConfig, ensureCommentsBaseline } from '../comentarios/instalar.js';
|
|
12
|
-
import { combineHooks, HARNESS_DENY,
|
|
12
|
+
import { combineHooks, HARNESS_DENY, harnessPermissions, mergeSettings } from './settings.js';
|
|
13
13
|
/** Rule de convenção com o que é específico do projeto (D-38): é do projeto, então fica fora do lock. */
|
|
14
14
|
export const PROJECT_RULE_PATH = '.claude/rules/harness-projeto.md';
|
|
15
15
|
/** Esqueleto da rule de projeto; os comandos `-legado` renomeados pelo update entram na última seção. */
|
|
@@ -165,12 +165,12 @@ export function executePlan(root, plan, pkg, now = new Date()) {
|
|
|
165
165
|
const permission = needsEventPermission(plan);
|
|
166
166
|
const hooks = combineHooks(plan.registerHooks ? projectHooksConfig() : {}, plan.registerComments ? commentsHooksConfig() : {});
|
|
167
167
|
if (Object.keys(hooks).length || permission) {
|
|
168
|
-
mergeSettings(root, { hooks, allow: permission ?
|
|
168
|
+
mergeSettings(root, { hooks, allow: permission ? harnessPermissions({ banco: bancoAtivo(plan) }) : [], deny: permission ? HARNESS_DENY : [] }, now);
|
|
169
169
|
}
|
|
170
170
|
if (plan.registerComments)
|
|
171
171
|
ensureCommentsBaseline(root);
|
|
172
172
|
if (plan.registerMcp)
|
|
173
|
-
mergeMcpServers(root, {
|
|
173
|
+
mergeMcpServers(root, { [BANCO_MCP_NOME]: bancoMcpServer(pkg.versao) }, now);
|
|
174
174
|
if (plan.mcpRecusado)
|
|
175
175
|
gravarRegistrarMcp(root, false);
|
|
176
176
|
const items = new Map();
|
|
@@ -193,6 +193,10 @@ export function executePlan(root, plan, pkg, now = new Date()) {
|
|
|
193
193
|
writeLock(root, lock);
|
|
194
194
|
return lock;
|
|
195
195
|
}
|
|
196
|
+
/** O banco não foi recusado (`--sem-banco-dados` ou `banco_dados.registrar_mcp: false`): as permissões do MCP entram. */
|
|
197
|
+
function bancoAtivo(plan) {
|
|
198
|
+
return !plan.mcpRecusado && plan.mcpSkipReason !== 'config';
|
|
199
|
+
}
|
|
196
200
|
/** Skills e agents do harness registram o rastro com `npx @fontdata/ai-harness evento`. */
|
|
197
201
|
function needsEventPermission(plan) {
|
|
198
202
|
return plan.items.some((item) => item.tipo === 'skill' || item.tipo === 'agent');
|
|
@@ -209,7 +213,7 @@ export function describePlan(plan) {
|
|
|
209
213
|
if (plan.registerHooks)
|
|
210
214
|
lines.push('Hooks do harness registrados em .claude/settings.json (sem duplicar os existentes).');
|
|
211
215
|
if (needsEventPermission(plan)) {
|
|
212
|
-
lines.push(`Permissões ${
|
|
216
|
+
lines.push(`Permissões ${harnessPermissions({ banco: bancoAtivo(plan) }).join(', ')} em .claude/settings.json, para o rastro, a checagem de conflitos, ` +
|
|
213
217
|
'o /git-commit-push, os sub-agents e as consultas de leitura não pedirem aprovação (inclusive no modo auto); ' +
|
|
214
218
|
'o push forçado fica bloqueado em permissions.deny.');
|
|
215
219
|
}
|
|
@@ -217,13 +221,13 @@ export function describePlan(plan) {
|
|
|
217
221
|
lines.push(`Servidor MCP de banco (somente leitura) registrado em ${MCP_PATH}, sem duplicar os servidores existentes.`);
|
|
218
222
|
}
|
|
219
223
|
else if (plan.mcpRecusado) {
|
|
220
|
-
lines.push(`Servidor MCP de banco não registrado (recusado); a escolha fica em
|
|
224
|
+
lines.push(`Servidor MCP de banco não registrado (recusado); a escolha fica em banco_dados.registrar_mcp: false no .ai-harness/config.json.`);
|
|
221
225
|
}
|
|
222
226
|
else if (plan.mcpSkipReason === 'plugin') {
|
|
223
227
|
lines.push(`Servidor MCP de banco não registrado em ${MCP_PATH}: o plugin fontdata@ai-harness já o fornece.`);
|
|
224
228
|
}
|
|
225
229
|
else if (plan.mcpSkipReason === 'config') {
|
|
226
|
-
lines.push(`Servidor MCP de banco não registrado em ${MCP_PATH}:
|
|
230
|
+
lines.push(`Servidor MCP de banco não registrado em ${MCP_PATH}: banco_dados.registrar_mcp está false no .ai-harness/config.json.`);
|
|
227
231
|
}
|
|
228
232
|
if (plan.registerComments) {
|
|
229
233
|
lines.push('Padrão de comentários: rule carregada em toda sessão, hook que bloqueia a violação e catraca do legado (gerada só na primeira instalação).');
|
package/dist/install/settings.js
CHANGED
|
@@ -33,11 +33,14 @@ export const READ_PERMISSIONS = [
|
|
|
33
33
|
'Bash(gh pr diff:*)',
|
|
34
34
|
'Bash(npm outdated:*)',
|
|
35
35
|
];
|
|
36
|
+
const BANCO_TOOLS = ['listar_conexoes', 'listar_tabelas', 'descrever_tabela', 'consultar'];
|
|
36
37
|
/**
|
|
37
|
-
* Ferramentas
|
|
38
|
-
*
|
|
38
|
+
* Ferramentas do servidor MCP de banco, registrado pelo `.mcp.json` (`banco_dados`) ou pelo plugin
|
|
39
|
+
* (`banco_dados`). A `consultar` também é liberada: os dados pessoais chegam mascarados.
|
|
39
40
|
*/
|
|
40
|
-
export const BANCO_PERMISSIONS = ['
|
|
41
|
+
export const BANCO_PERMISSIONS = ['mcp__banco_dados__', 'mcp__plugin_fontdata_banco_dados__'].flatMap((prefixo) => BANCO_TOOLS.map((ferramenta) => `${prefixo}${ferramenta}`));
|
|
42
|
+
/** As mesmas regras com o nome antigo do servidor (`banco`, até a 2.7.0); o update as remove. */
|
|
43
|
+
export const BANCO_PERMISSIONS_LEGADAS = ['mcp__banco__', 'mcp__plugin_fontdata_banco__'].flatMap((prefixo) => BANCO_TOOLS.map((ferramenta) => `${prefixo}${ferramenta}`));
|
|
41
44
|
/** Regras de `permissions.allow` que o init e o update gravam para as skills e os agents do harness. */
|
|
42
45
|
export const HARNESS_PERMISSIONS = [
|
|
43
46
|
EVENT_PERMISSION,
|
|
@@ -48,6 +51,10 @@ export const HARNESS_PERMISSIONS = [
|
|
|
48
51
|
...READ_PERMISSIONS,
|
|
49
52
|
...BANCO_PERMISSIONS,
|
|
50
53
|
];
|
|
54
|
+
/** As regras do harness; sem o banco (recusado com `--sem-banco-dados` ou `banco_dados.registrar_mcp: false`), sem as do MCP. */
|
|
55
|
+
export function harnessPermissions({ banco }) {
|
|
56
|
+
return banco ? HARNESS_PERMISSIONS : HARNESS_PERMISSIONS.filter((rule) => !BANCO_PERMISSIONS.includes(rule));
|
|
57
|
+
}
|
|
51
58
|
/** O push liberado não vale para reescrever o histórico remoto: `deny` vence o `allow`. */
|
|
52
59
|
export const HARNESS_DENY = [
|
|
53
60
|
'Bash(git push --force:*)',
|
|
@@ -92,7 +99,7 @@ export function mergeHooksIntoSettings(root, hooks, now = new Date()) {
|
|
|
92
99
|
* Acrescenta hooks e regras de `permissions.allow` ao `.claude/settings.json` do projeto
|
|
93
100
|
* sem perder o que já existe. Um hook é considerado presente quando algum grupo do mesmo
|
|
94
101
|
* evento já tem o mesmo `command`, e uma regra quando já está na lista, o que torna a
|
|
95
|
-
* operação idempotente. Tudo é gravado de uma vez, com um único backup datado do arquivo
|
|
102
|
+
* operação idempotente. As regras de `removeAllow` saem de `permissions.allow`. Tudo é gravado de uma vez, com um único backup datado do arquivo
|
|
96
103
|
* original; JSON inválido é recusado sem tocar em nada.
|
|
97
104
|
*/
|
|
98
105
|
export function mergeSettings(root, additions, now = new Date()) {
|
|
@@ -155,6 +162,12 @@ export function mergeSettings(root, additions, now = new Date()) {
|
|
|
155
162
|
}
|
|
156
163
|
next.permissions = { ...permissions, [key]: list };
|
|
157
164
|
}
|
|
165
|
+
const remove = additions.removeAllow ?? [];
|
|
166
|
+
const currentAllow = next.permissions?.allow;
|
|
167
|
+
if (remove.length && currentAllow?.some((rule) => remove.includes(rule))) {
|
|
168
|
+
next.permissions = { ...next.permissions, allow: currentAllow.filter((rule) => !remove.includes(rule)) };
|
|
169
|
+
changed = true;
|
|
170
|
+
}
|
|
158
171
|
if (!changed)
|
|
159
172
|
return { changed: false, backup: null };
|
|
160
173
|
let backup = null;
|
package/dist/update/update.js
CHANGED
|
@@ -2,15 +2,15 @@ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync
|
|
|
2
2
|
import { dirname, join, resolve } from 'node:path';
|
|
3
3
|
import { allCommands, loadCatalog } from '../catalog.js';
|
|
4
4
|
import { commentsHooksConfig, ensureCommentsBaseline } from '../comentarios/instalar.js';
|
|
5
|
-
import { CONFIG_PATH, gravarRegistrarMcp, readConfig, syncGitignore, writeDefaultConfig } from '../config/config.js';
|
|
5
|
+
import { CONFIG_PATH, gravarRegistrarMcp, migrarSecaoBanco, readConfig, syncGitignore, temSecaoBancoLegada, writeDefaultConfig, } from '../config/config.js';
|
|
6
6
|
import { applyAtomically } from '../install/atomic.js';
|
|
7
7
|
import { suggestFlag } from '../install/flags.js';
|
|
8
8
|
import { HOOKS_ITEM_ID, isHarnessPluginEnabled, projectHooksConfig } from '../install/hooks.js';
|
|
9
|
-
import { bancoMcpServer, ehEntradaGerenciada, MCP_PATH, mergeMcpServers } from '../install/mcp.js';
|
|
9
|
+
import { BANCO_MCP_NOME, BANCO_MCP_NOME_LEGADO, bancoMcpServer, ehEntradaGerenciada, MCP_PATH, mergeMcpServers } from '../install/mcp.js';
|
|
10
10
|
import { hashContent, LOCK_PATH, LOCK_VERSION, readLock, writeLock } from '../install/lock.js';
|
|
11
11
|
import { itemSources, packageInfo, PROJECT_RULE_PATH, projectRuleContent } from '../install/plan.js';
|
|
12
12
|
import { neededSkillsFor } from '../install/selection.js';
|
|
13
|
-
import { combineHooks, HARNESS_DENY,
|
|
13
|
+
import { BANCO_PERMISSIONS_LEGADAS, combineHooks, HARNESS_DENY, harnessPermissions, hasAllowRule, mergeSettings, SETTINGS_PATH, } from '../install/settings.js';
|
|
14
14
|
import { adoptLegacyInstall } from './legado.js';
|
|
15
15
|
function isPlainObject(value) {
|
|
16
16
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -32,7 +32,9 @@ function mcpChange(root, versao) {
|
|
|
32
32
|
const servers = parsed.mcpServers === undefined ? {} : parsed.mcpServers;
|
|
33
33
|
if (!isPlainObject(servers))
|
|
34
34
|
return 'invalido';
|
|
35
|
-
|
|
35
|
+
if (ehEntradaGerenciada(servers[BANCO_MCP_NOME_LEGADO]))
|
|
36
|
+
return 'renomear';
|
|
37
|
+
const atual = servers[BANCO_MCP_NOME];
|
|
36
38
|
if (atual === undefined)
|
|
37
39
|
return 'faltando';
|
|
38
40
|
const pin = bancoMcpServer(versao).args.find((arg) => arg.startsWith('@fontdata/'));
|
|
@@ -136,11 +138,13 @@ export function checkUpdate(root, catalog = loadCatalog(), legacy, home, { semBa
|
|
|
136
138
|
addItem('rule', 'comentarios', true);
|
|
137
139
|
addItem('hook', 'comentarios', true);
|
|
138
140
|
}
|
|
141
|
+
const registrarMcpConfig = readConfig(root).config.banco_dados.registrar_mcp;
|
|
142
|
+
const permissoesBanco = !semBanco && registrarMcpConfig;
|
|
139
143
|
const permissoesFaltando = [
|
|
140
|
-
...
|
|
144
|
+
...harnessPermissions({ banco: permissoesBanco }).filter((rule) => !hasAllowRule(root, rule)),
|
|
141
145
|
...HARNESS_DENY.filter((rule) => !hasAllowRule(root, rule, 'deny')).map((rule) => `deny ${rule}`),
|
|
142
146
|
];
|
|
143
|
-
const
|
|
147
|
+
const permissoesLegadas = BANCO_PERMISSIONS_LEGADAS.filter((rule) => hasAllowRule(root, rule));
|
|
144
148
|
const registrarMcp = !semBanco && registrarMcpConfig && !isHarnessPluginEnabled(root, home);
|
|
145
149
|
return {
|
|
146
150
|
changes,
|
|
@@ -151,9 +155,12 @@ export function checkUpdate(root, catalog = loadCatalog(), legacy, home, { semBa
|
|
|
151
155
|
registrarComentarios: adotada,
|
|
152
156
|
permissaoAusente: permissoesFaltando.length > 0,
|
|
153
157
|
permissoesFaltando,
|
|
158
|
+
permissoesLegadas,
|
|
159
|
+
permissoesBanco,
|
|
154
160
|
registrarMcp,
|
|
155
161
|
mudancaMcp: registrarMcp ? mcpChange(root, packageInfo().versao) : null,
|
|
156
162
|
recusarMcp: semBanco && registrarMcpConfig,
|
|
163
|
+
renomearSecaoBanco: temSecaoBancoLegada(root),
|
|
157
164
|
};
|
|
158
165
|
}
|
|
159
166
|
/**
|
|
@@ -192,7 +199,10 @@ export function applyUpdate(root, report, options, pkg, now = new Date()) {
|
|
|
192
199
|
return { de: path, para };
|
|
193
200
|
});
|
|
194
201
|
const hooks = combineHooks(report.registrarHooks ? projectHooksConfig() : {}, report.registrarComentarios ? commentsHooksConfig() : {});
|
|
195
|
-
|
|
202
|
+
const ajustes = [];
|
|
203
|
+
const settings = mergeSettings(root, { hooks, allow: harnessPermissions({ banco: report.permissoesBanco }), deny: HARNESS_DENY, removeAllow: BANCO_PERMISSIONS_LEGADAS }, now);
|
|
204
|
+
if (settings.changed)
|
|
205
|
+
ajustes.push(`${SETTINGS_PATH} atualizado${settings.backup ? ` (backup em ${settings.backup})` : ''}.`);
|
|
196
206
|
if (report.registrarComentarios)
|
|
197
207
|
ensureCommentsBaseline(root);
|
|
198
208
|
if (report.adotada) {
|
|
@@ -211,14 +221,23 @@ export function applyUpdate(root, report, options, pkg, now = new Date()) {
|
|
|
211
221
|
else if (report.registrarMcp) {
|
|
212
222
|
// Uma falha no .mcp.json nunca deixa o update pela metade, sem lock.
|
|
213
223
|
try {
|
|
214
|
-
mergeMcpServers(root, {
|
|
224
|
+
const mcp = mergeMcpServers(root, { [BANCO_MCP_NOME]: bancoMcpServer(pkg.versao) }, now, {
|
|
225
|
+
atualizarGerenciada: true,
|
|
226
|
+
removerGerenciadas: [BANCO_MCP_NOME_LEGADO],
|
|
227
|
+
});
|
|
228
|
+
if (mcp.changed)
|
|
229
|
+
ajustes.push(`${MCP_PATH} com mcpServers.${BANCO_MCP_NOME} na versão ${pkg.versao}${mcp.backup ? ` (backup em ${mcp.backup})` : ''}.`);
|
|
215
230
|
}
|
|
216
231
|
catch (error) {
|
|
217
232
|
avisos.push(error instanceof Error ? error.message : String(error));
|
|
218
233
|
}
|
|
219
234
|
}
|
|
220
|
-
if (report.
|
|
235
|
+
if (report.renomearSecaoBanco && migrarSecaoBanco(root))
|
|
236
|
+
ajustes.push(`seção banco renomeada para banco_dados em ${CONFIG_PATH}.`);
|
|
237
|
+
if (report.recusarMcp) {
|
|
221
238
|
gravarRegistrarMcp(root, false);
|
|
239
|
+
ajustes.push(`banco_dados.registrar_mcp: false gravado em ${CONFIG_PATH}.`);
|
|
240
|
+
}
|
|
222
241
|
const itemOf = new Map(report.changes.map((change) => [change.caminho, change.item]));
|
|
223
242
|
const writtenHash = new Map(write.map((file) => [file.path, hashContent(file.content)]));
|
|
224
243
|
const dropped = new Set([...remove, ...report.stale, ...legacy]);
|
|
@@ -252,13 +271,14 @@ export function applyUpdate(root, report, options, pkg, now = new Date()) {
|
|
|
252
271
|
itens.push({ ...added, arquivos: added.arquivos.sort((a, b) => a.caminho.localeCompare(b.caminho)) });
|
|
253
272
|
}
|
|
254
273
|
writeLock(root, { lock_versao: LOCK_VERSION, pacote: pkg, gerado_em: now.toISOString(), itens });
|
|
255
|
-
return { written: write.map((file) => file.path), removed: remove, kept, renamed, avisos };
|
|
274
|
+
return { written: write.map((file) => file.path), removed: remove, kept, renamed, avisos, ajustes };
|
|
256
275
|
}
|
|
257
276
|
const MCP_INVALIDO = `O ${MCP_PATH} está com JSON inválido: o registro do MCP de banco foi pulado e o arquivo não foi tocado. Corrija-o e rode o update de novo.`;
|
|
258
277
|
const MCP_DESCRIPTIONS = {
|
|
259
278
|
ausente: 'não existe',
|
|
260
|
-
faltando:
|
|
261
|
-
versao:
|
|
279
|
+
faltando: `não tem a entrada ${BANCO_MCP_NOME}`,
|
|
280
|
+
versao: `fixa outra versão do servidor ${BANCO_MCP_NOME}`,
|
|
281
|
+
renomear: `tem a entrada ${BANCO_MCP_NOME_LEGADO} do harness, com o nome antigo; ela é trocada pela ${BANCO_MCP_NOME}`,
|
|
262
282
|
};
|
|
263
283
|
const DESCRIPTIONS = {
|
|
264
284
|
novo: 'novo no pacote, será gravado',
|
|
@@ -283,27 +303,32 @@ export function describeUpdate(report) {
|
|
|
283
303
|
`Faltam permissões do harness em .claude/settings.json (${report.permissoesFaltando.join(', ')}); o --yes as acrescenta.`,
|
|
284
304
|
]
|
|
285
305
|
: [];
|
|
306
|
+
const legadas = report.permissoesLegadas.length
|
|
307
|
+
? [`Permissões do MCP de banco com o nome antigo em .claude/settings.json (${report.permissoesLegadas.join(', ')}); o --yes as remove.`]
|
|
308
|
+
: [];
|
|
286
309
|
const mcp = [
|
|
287
310
|
...(report.mudancaMcp === 'invalido'
|
|
288
311
|
? [`O ${MCP_PATH} está com JSON inválido; o --yes pula o registro do MCP de banco e não toca no arquivo.`]
|
|
289
312
|
: report.mudancaMcp
|
|
290
|
-
? [`O ${MCP_PATH} ${MCP_DESCRIPTIONS[report.mudancaMcp]}; o --yes grava mcpServers
|
|
313
|
+
? [`O ${MCP_PATH} ${MCP_DESCRIPTIONS[report.mudancaMcp]}; o --yes grava mcpServers.${BANCO_MCP_NOME} na versão ${packageInfo().versao}.`]
|
|
291
314
|
: []),
|
|
292
|
-
...(report.
|
|
315
|
+
...(report.renomearSecaoBanco ? [`A seção banco de ${CONFIG_PATH} agora se chama banco_dados; o --yes a renomeia.`] : []),
|
|
316
|
+
...(report.recusarMcp ? [`--sem-banco-dados: o --yes grava banco_dados.registrar_mcp: false em ${CONFIG_PATH} e não registra o MCP.`] : []),
|
|
293
317
|
];
|
|
294
318
|
if (!report.changes.length) {
|
|
295
|
-
const status = adopted.length || permission.length || mcp.length ? 'Nenhum arquivo a atualizar.' : 'A instalação está em dia: nenhum arquivo a atualizar.';
|
|
296
|
-
return [...adopted, ...permission, ...mcp, status].join('\n');
|
|
319
|
+
const status = adopted.length || permission.length || legadas.length || mcp.length ? 'Nenhum arquivo a atualizar.' : 'A instalação está em dia: nenhum arquivo a atualizar.';
|
|
320
|
+
return [...adopted, ...permission, ...legadas, ...mcp, status].join('\n');
|
|
297
321
|
}
|
|
298
322
|
return [
|
|
299
323
|
...adopted,
|
|
300
324
|
...permission,
|
|
325
|
+
...legadas,
|
|
301
326
|
...mcp,
|
|
302
327
|
`Mudanças em relação ao pacote ${packageInfo().versao} (instalado: ${report.lock.pacote.versao}):`,
|
|
303
328
|
...report.changes.map((change) => ` ${change.tipo.padEnd(16)} ${change.caminho}${change.destino ? ` → ${change.destino}` : ''} (${DESCRIPTIONS[change.tipo]})`),
|
|
304
329
|
].join('\n');
|
|
305
330
|
}
|
|
306
|
-
const UPDATE_FLAGS = ['--check', '--yes', '-y', '--sim', '--forcar', '--sem-banco'];
|
|
331
|
+
const UPDATE_FLAGS = ['--check', '--yes', '-y', '--sim', '--forcar', '--sem-banco-dados'];
|
|
307
332
|
export function parseUpdateArgs(argv) {
|
|
308
333
|
const args = { path: undefined, check: false, yes: false, force: false, semBanco: false };
|
|
309
334
|
for (const token of argv) {
|
|
@@ -316,7 +341,7 @@ export function parseUpdateArgs(argv) {
|
|
|
316
341
|
args.check = true;
|
|
317
342
|
else if (token === '--forcar')
|
|
318
343
|
args.force = true;
|
|
319
|
-
else if (token === '--sem-banco')
|
|
344
|
+
else if (token === '--sem-banco-dados')
|
|
320
345
|
args.semBanco = true;
|
|
321
346
|
else if (token === '--yes' || token === '-y' || token === '--sim')
|
|
322
347
|
args.yes = true;
|
|
@@ -327,7 +352,7 @@ export function parseUpdateArgs(argv) {
|
|
|
327
352
|
}
|
|
328
353
|
return args;
|
|
329
354
|
}
|
|
330
|
-
/** `ai-harness update [caminho] [--check] [--yes] [--forcar] [--sem-banco]`; sem `--yes` só simula. */
|
|
355
|
+
/** `ai-harness update [caminho] [--check] [--yes] [--forcar] [--sem-banco-dados]`; sem `--yes` só simula. */
|
|
331
356
|
export function runUpdateCommand(argv) {
|
|
332
357
|
const args = parseUpdateArgs(argv);
|
|
333
358
|
const root = resolve(process.cwd(), args.path ?? '.');
|
|
@@ -336,7 +361,7 @@ export function runUpdateCommand(argv) {
|
|
|
336
361
|
const report = checkUpdate(root, undefined, undefined, undefined, { semBanco: args.semBanco });
|
|
337
362
|
console.log(describeUpdate(report));
|
|
338
363
|
// Sem arquivo a atualizar, a adoção ainda grava o lock, e a permissão e o MCP ainda são acrescentados.
|
|
339
|
-
const pendente = report.adotada || report.permissaoAusente || report.mudancaMcp !== null || report.recusarMcp;
|
|
364
|
+
const pendente = report.adotada || report.permissaoAusente || report.permissoesLegadas.length > 0 || report.mudancaMcp !== null || report.recusarMcp || report.renomearSecaoBanco;
|
|
340
365
|
if (args.check || (!report.changes.length && !pendente))
|
|
341
366
|
return 0;
|
|
342
367
|
if (!args.yes) {
|
|
@@ -347,6 +372,8 @@ export function runUpdateCommand(argv) {
|
|
|
347
372
|
const result = applyUpdate(root, report, { force: args.force }, packageInfo());
|
|
348
373
|
console.log(`\nAtualização aplicada: ${result.written.length} gravado(s), ${result.removed.length} apagado(s), ` +
|
|
349
374
|
`${result.renamed.length} renomeado(s), ${result.kept.length} preservado(s).`);
|
|
375
|
+
for (const ajuste of result.ajustes)
|
|
376
|
+
console.log(`Gravado: ${ajuste}`);
|
|
350
377
|
for (const aviso of result.avisos)
|
|
351
378
|
console.log(`Aviso: ${aviso}`);
|
|
352
379
|
if (result.renamed.length) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fontdata/ai-harness",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.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": {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|