@fontdata/ai-harness 3.2.1 → 3.2.3

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "fontdata",
3
- "version": "3.2.1",
3
+ "version": "3.2.3",
4
4
  "mcpServers": {
5
- "banco_dados": { "command": "npx", "args": ["-y", "@fontdata/ai-harness@3.2.1", "banco-dados-mcp"] }
5
+ "banco_dados": { "command": "npx", "args": ["-y", "@fontdata/ai-harness@3.2.3", "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
@@ -95,7 +95,7 @@ npx @fontdata/ai-harness init --comandos resolver-bug,nova-tarefa --sem-harness
95
95
  | `--comandos a,b,c` | Seleção manual |
96
96
  | `--sem-harness` | Não inclui o comando-mestre `/harness` |
97
97
  | `--sem-hooks` / `--sem-agents` | Não instala hooks / agents |
98
- | `--sem-comentarios` | Não instala a rule de comentários, o hook e a catraca do legado |
98
+ | `--sem-comentarios` | Não instala a rule de comentários, o hook e a catraca do legado e lembra a recusa no config |
99
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`) |
@@ -157,7 +157,7 @@ projeto, cadastro, limites do filtro, produção, mascaramento e dúvidas freque
157
157
  |---|---|
158
158
  | `init [caminho]` | Instala comandos, skills, agents e hooks no projeto |
159
159
  | `doctor [caminho]` | Diagnostica instalação, contrato dos documentos, rastro, bloqueios antigos, citações às pastas antigas de `docs/` no código e versão do Node |
160
- | `update [caminho]` | Atualiza o que foi instalado sem sobrescrever alterações locais (`--check`, `--yes`, `--forcar`) |
160
+ | `update [caminho]` | Atualiza o que foi instalado sem sobrescrever alterações locais (`--check`, `--yes`, `--forcar`, `--sem-banco-dados`, `--sem-comentarios`) |
161
161
  | `migrar [caminho]` | Move os documentos do layout anterior para `docs/trabalhos/<id>/` (`--yes`); `--reclassificar` corrige planos migrados que ficaram em execução |
162
162
  | `memoria [caminho]` | Índice de quem mexeu em cada arquivo (`--arquivo <caminho>`, `--regressao <id>`) |
163
163
  | `evento <evento>` | Grava no rastro do trabalho (usado por fluxos e agents) |
@@ -248,7 +248,12 @@ também acionado pelo modelo quando o pedido combina.
248
248
  ### Setup guiado
249
249
  - `/harness` **(skill)** — conduz o setup em etapas (ideia, contexto, ignores, plano do start, scaffold, peças do harness, auto-tag e execução); `/harness <etapa>` roda uma etapa isolada
250
250
 
251
- O padrão de comentários não é um comando: a rule, o hook e a catraca do legado vêm do `init` e do `update`.
251
+ O padrão de comentários não é um comando: a rule, o hook e a catraca do legado vêm do `init` e do `update`. O
252
+ `update` também o instala em projetos cujo lock é anterior a ele; um hook próprio que já existe, de versão antiga ou
253
+ adaptado, é mantido e só entra no lock. Para recusar, use `--sem-comentarios` no `init` ou
254
+ no `update`: a escolha fica em `comentarios.instalar: false` no `.ai-harness/config.json`, e o `update` a respeita.
255
+ Para reativar, apague a chave e rode `update --yes` (ou `npx @fontdata/ai-harness comentarios --yes`). Sem o padrão
256
+ e sem a recusa, o `doctor` avisa com `comentarios-ausente`.
252
257
 
253
258
  A skill `lgpd-checker`, usada pela auditoria de segurança, é uma cópia vendorizada de
254
259
  [bittencourtthulio/lgpd-checker](https://github.com/bittencourtthulio/lgpd-checker); veja
package/dist/cli.js CHANGED
@@ -52,6 +52,7 @@ Gerenciar:
52
52
  init [caminho] Instala os slash commands em .claude/commands do projeto (interativo)
53
53
  doctor [caminho] Diagnostica a instalação (arquivos, lock, skills, agents, hooks, Node)
54
54
  update [caminho] Atualiza o que foi instalado: --check só lista, --yes aplica, --forcar sobrescreve conflitos
55
+ --sem-comentarios recusa o padrão de comentários (grava comentarios.instalar: false)
55
56
  --sem-banco-dados: não registra o MCP de banco no .mcp.json e lembra a recusa no config
56
57
  migrar [caminho] Move docs/specs, plans, bugs e investigacoes para docs/trabalhos/<id>/ (--yes aplica)
57
58
  --reclassificar: planos já migrados em em-execucao viram concluido ou legado
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { EOL } from 'node:os';
3
3
  import { join, resolve } from 'node:path';
4
4
  import { templatesDir } from '../catalog.js';
5
+ import { gravarInstalarComentarios } from '../config/config.js';
5
6
  import { suggestFlag } from '../install/flags.js';
6
7
  import { hashContent } from '../install/lock.js';
7
8
  import { executePlan, packageInfo } from '../install/plan.js';
@@ -66,6 +67,8 @@ export function runCommentsCommand(argv) {
66
67
  };
67
68
  executePlan(root, plan, packageInfo());
68
69
  mergeSettings(root, { hooks: commentsHooksConfig() });
70
+ // Instalar pelo comando desfaz uma recusa guardada pelo `--sem-comentarios`.
71
+ gravarInstalarComentarios(root, true);
69
72
  const legacy = ensureCommentsBaseline(root);
70
73
  if (legacy !== null) {
71
74
  console.log(`${EOL}Catraca gerada: ${legacy} arquivo(s) legado(s) com violações toleradas. Eles não precisam ser limpos de uma vez, só não podem piorar.`);
@@ -72,6 +72,7 @@ export function defaultConfig() {
72
72
  projeto: {},
73
73
  declarado: false,
74
74
  },
75
+ comentarios: { instalar: true },
75
76
  };
76
77
  }
77
78
  function inteiroEntre(value, min, max) {
@@ -232,6 +233,12 @@ export function readConfig(root) {
232
233
  else
233
234
  problems.push(`bloqueios.dias_para_aviso deve ser um inteiro positivo em ${CONFIG_PATH}.`);
234
235
  }
236
+ if (isObject(raw.comentarios) && 'instalar' in raw.comentarios) {
237
+ if (typeof raw.comentarios.instalar === 'boolean')
238
+ config.comentarios.instalar = raw.comentarios.instalar;
239
+ else
240
+ problems.push(`comentarios.instalar deve ser true ou false em ${CONFIG_PATH}.`);
241
+ }
235
242
  const secao = soSecaoLegada(raw) ? raw[BANCO_CHAVE_LEGADA] : raw[BANCO_CHAVE];
236
243
  if (isObject(secao))
237
244
  lerBanco(secao, config.banco_dados, problems);
@@ -243,14 +250,18 @@ export function readConfig(root) {
243
250
  }
244
251
  return { config, problems };
245
252
  }
246
- /** Cria a config com os padrões, sem a seção banco_dados; retorna false se já existia (nunca sobrescreve). */
253
+ /** Os padrões que vão para o arquivo: sem as seções banco_dados e comentarios, que só entram quando mudam. */
254
+ function configGravada() {
255
+ const { banco_dados: _banco, comentarios: _comentarios, ...gravada } = defaultConfig();
256
+ return gravada;
257
+ }
258
+ /** Cria a config com os padrões, sem as seções banco_dados e comentarios; retorna false se já existia (nunca sobrescreve). */
247
259
  export function writeDefaultConfig(root) {
248
260
  const file = join(root, CONFIG_PATH);
249
261
  if (existsSync(file))
250
262
  return false;
251
263
  mkdirSync(dirname(file), { recursive: true });
252
- const { banco_dados: _banco, ...gravada } = defaultConfig();
253
- writeFileSync(file, `${JSON.stringify(gravada, null, 2)}\n`);
264
+ writeFileSync(file, `${JSON.stringify(configGravada(), null, 2)}\n`);
254
265
  return true;
255
266
  }
256
267
  function lerJsonParaEscrita(file, chave) {
@@ -336,8 +347,7 @@ export function gravarRegistrarMcp(root, valor) {
336
347
  raw = lerJsonParaEscrita(file, 'banco_dados.registrar_mcp');
337
348
  }
338
349
  else {
339
- const { banco_dados: _banco, ...padrao } = defaultConfig();
340
- raw = padrao;
350
+ raw = configGravada();
341
351
  }
342
352
  const banco = secaoBanco(raw);
343
353
  if (banco.registrar_mcp === valor)
@@ -345,6 +355,24 @@ export function gravarRegistrarMcp(root, valor) {
345
355
  mkdirSync(dirname(file), { recursive: true });
346
356
  writeFileSync(file, `${JSON.stringify(comSecaoBanco(raw, { ...banco, registrar_mcp: valor }), null, 2)}\n`);
347
357
  }
358
+ /**
359
+ * Grava a recusa do padrão de comentários (`comentarios.instalar: false`), preservando o resto do JSON; sem config,
360
+ * cria uma com os padrões e essa chave. `true` desfaz a recusa apagando a chave, e não cria o arquivo. JSON inválido
361
+ * é recusado sem tocar no arquivo.
362
+ */
363
+ export function gravarInstalarComentarios(root, valor) {
364
+ const file = join(root, CONFIG_PATH);
365
+ if (!existsSync(file) && valor)
366
+ return;
367
+ const raw = existsSync(file) ? lerJsonParaEscrita(file, 'comentarios.instalar') : configGravada();
368
+ const { instalar: atual, ...resto } = isObject(raw.comentarios) ? raw.comentarios : {};
369
+ if (valor ? atual === undefined || atual === true : atual === false)
370
+ return;
371
+ const { comentarios: _comentarios, ...semSecao } = raw;
372
+ const proxima = valor ? (Object.keys(resto).length ? { ...raw, comentarios: resto } : semSecao) : { ...raw, comentarios: { ...resto, instalar: false } };
373
+ mkdirSync(dirname(file), { recursive: true });
374
+ writeFileSync(file, `${JSON.stringify(proxima, null, 2)}\n`);
375
+ }
348
376
  const BLOCK_START = '# ai-harness (gerenciado pelo harness; não edite entre estas linhas)';
349
377
  const BLOCK_END = '# /ai-harness';
350
378
  /**
@@ -4,6 +4,7 @@ import { join } from 'node:path';
4
4
  import { lerEnvSettingsLocal, lerVariavel, SETTINGS_LOCAL_PATH } from '../banco-dados/ambiente.js';
5
5
  import { caminhoCadastro, lerCadastro } from '../banco-dados/cadastro.js';
6
6
  import { allCommands, loadCatalog } from '../catalog.js';
7
+ import { COMMENTS_HOOK_PATH } from '../comentarios/instalar.js';
7
8
  import { CONFIG_PATH, readConfig } from '../config/config.js';
8
9
  import { readArtifact, validateAllWorks, WORKS_DIR } from '../contratos/artefatos.js';
9
10
  import { validateEventLog } from '../contratos/eventos.js';
@@ -390,6 +391,24 @@ function checkOldCommentsBlock(root) {
390
391
  },
391
392
  ];
392
393
  }
394
+ /**
395
+ * Lock anterior ao padrão de comentários e sem a recusa no config: o update o instala. Hook no disco fora do lock é
396
+ * instalação antiga ou adaptada, que o update só registra no lock.
397
+ */
398
+ function checkComments(root, lock, config) {
399
+ if (!config.comentarios.instalar || existsSync(join(root, COMMENTS_HOOK_PATH)))
400
+ return [];
401
+ if (lock.itens.some((item) => item.tipo === 'hook' && item.id === 'comentarios'))
402
+ return [];
403
+ return [
404
+ {
405
+ id: 'comentarios-ausente',
406
+ severidade: 'aviso',
407
+ mensagem: 'O padrão de comentários (rule, hook e catraca do legado) não está instalado, e a recusa não está registrada.',
408
+ correcao: `Instale com \`npx @fontdata/ai-harness update --yes\`, ou recuse com \`update --sem-comentarios --yes\`, que grava comentarios.instalar: false no ${CONFIG_PATH}.`,
409
+ },
410
+ ];
411
+ }
393
412
  function checkConfig(problems) {
394
413
  return problems.map((mensagem) => ({
395
414
  id: 'config-invalida',
@@ -507,7 +526,7 @@ export function runDoctor(root, options = {}) {
507
526
  return [
508
527
  ...checkNode(options.nodeVersion ?? process.versions.node),
509
528
  ...lockFindings,
510
- ...(lock ? [...checkFiles(root, lock), ...checkRequiredSkills(root, lock)] : []),
529
+ ...(lock ? [...checkFiles(root, lock), ...checkRequiredSkills(root, lock), ...checkComments(root, lock, config)] : []),
511
530
  ...checkShadowedCommands(root),
512
531
  ...checkOldCommentsBlock(root),
513
532
  ...checkAgents(root),
@@ -7,7 +7,7 @@ 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
9
  import { BANCO_MCP_NOME, bancoMcpServer, MCP_PATH, mergeMcpServers } from './mcp.js';
10
- import { gravarRegistrarMcp } from '../config/config.js';
10
+ import { gravarInstalarComentarios, gravarRegistrarMcp } from '../config/config.js';
11
11
  import { COMMENTS_RULE_PATH, commentsHooksConfig, ensureCommentsBaseline } from '../comentarios/instalar.js';
12
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. */
@@ -107,6 +107,7 @@ export function planInstall(catalog, root, options) {
107
107
  claudeMdBackup: false,
108
108
  registerHooks: Boolean(options.hooks),
109
109
  registerComments: Boolean(options.comments),
110
+ comentariosRecusados: !options.comments,
110
111
  };
111
112
  const add = (tipo, id) => {
112
113
  const paths = [];
@@ -173,6 +174,10 @@ export function executePlan(root, plan, pkg, now = new Date()) {
173
174
  mergeMcpServers(root, { [BANCO_MCP_NOME]: bancoMcpServer(pkg.versao) }, now);
174
175
  if (plan.mcpRecusado)
175
176
  gravarRegistrarMcp(root, false);
177
+ if (plan.comentariosRecusados)
178
+ gravarInstalarComentarios(root, false);
179
+ else if (plan.registerComments)
180
+ gravarInstalarComentarios(root, true);
176
181
  const items = new Map();
177
182
  for (const item of previous?.itens ?? [])
178
183
  items.set(`${item.tipo}:${item.id}`, item);
@@ -232,5 +237,8 @@ export function describePlan(plan) {
232
237
  if (plan.registerComments) {
233
238
  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).');
234
239
  }
240
+ else if (plan.comentariosRecusados) {
241
+ lines.push(`Padrão de comentários não instalado (recusado); a escolha fica em comentarios.instalar: false no .ai-harness/config.json, e o update a respeita.`);
242
+ }
235
243
  return lines.join('\n');
236
244
  }
@@ -1,8 +1,8 @@
1
1
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
2
2
  import { dirname, join, resolve } from 'node:path';
3
- import { allCommands, loadCatalog } from '../catalog.js';
4
- import { commentsHooksConfig, ensureCommentsBaseline } from '../comentarios/instalar.js';
5
- import { CONFIG_PATH, gravarRegistrarMcp, migrarSecaoBanco, readConfig, syncGitignore, temSecaoBancoLegada, writeDefaultConfig, } from '../config/config.js';
3
+ import { allCommands, loadCatalog, templatesDir } from '../catalog.js';
4
+ import { COMMENTS_HOOK_PATH, commentsHooksConfig, ensureCommentsBaseline } from '../comentarios/instalar.js';
5
+ import { CONFIG_PATH, gravarInstalarComentarios, 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';
@@ -50,7 +50,13 @@ function legacyPath(root, path) {
50
50
  return candidate;
51
51
  }
52
52
  }
53
- export function checkUpdate(root, catalog = loadCatalog(), legacy, home, { semBanco = false } = {}) {
53
+ const COMMENTS_HOOK_TEMPLATE = join(templatesDir, 'hooks', 'comentarios', 'check.mjs');
54
+ /** O hook de comentários existe no projeto com conteúdo diferente do template do pacote. */
55
+ function hookProprio(root) {
56
+ const local = join(root, COMMENTS_HOOK_PATH);
57
+ return existsSync(local) && hashContent(readFileSync(local)) !== hashContent(readFileSync(COMMENTS_HOOK_TEMPLATE));
58
+ }
59
+ export function checkUpdate(root, catalog = loadCatalog(), legacy, home, { semBanco = false, semComentarios = false } = {}) {
54
60
  const recorded = readLock(root);
55
61
  const lock = recorded ?? adoptLegacyInstall(root, legacy);
56
62
  if (!lock) {
@@ -127,6 +133,7 @@ export function checkUpdate(root, catalog = loadCatalog(), legacy, home, { semBa
127
133
  addItem('skill', skill.id, false);
128
134
  }
129
135
  }
136
+ const { config } = readConfig(root);
130
137
  // Na adoção, a instalação antiga ganha o que a 2.1 instala e ela não tinha.
131
138
  const adotada = !recorded;
132
139
  const pluginHooks = adotada && isHarnessPluginEnabled(root, home);
@@ -135,10 +142,18 @@ export function checkUpdate(root, catalog = loadCatalog(), legacy, home, { semBa
135
142
  addItem('agent', agent.id, true);
136
143
  if (!pluginHooks)
137
144
  addItem('hook', HOOKS_ITEM_ID, true);
145
+ }
146
+ // O padrão de comentários também chega ao lock anterior a ele, salvo com a recusa no config ou na flag. O hook é a
147
+ // referência: o comando `comentarios` não registra a rule quando o projeto já tem uma própria.
148
+ const querComentarios = !semComentarios && config.comentarios.instalar;
149
+ const hookNoLock = trackedItems.has('hook:comentarios');
150
+ const registrarHookComentarios = querComentarios && !hookNoLock && hookProprio(root);
151
+ const registrarComentarios = querComentarios && !registrarHookComentarios && (adotada || !hookNoLock);
152
+ if (registrarComentarios) {
138
153
  addItem('rule', 'comentarios', true);
139
154
  addItem('hook', 'comentarios', true);
140
155
  }
141
- const registrarMcpConfig = readConfig(root).config.banco_dados.registrar_mcp;
156
+ const registrarMcpConfig = config.banco_dados.registrar_mcp;
142
157
  const permissoesBanco = !semBanco && registrarMcpConfig;
143
158
  const permissoesFaltando = [
144
159
  ...harnessPermissions({ banco: permissoesBanco }).filter((rule) => !hasAllowRule(root, rule)),
@@ -152,7 +167,9 @@ export function checkUpdate(root, catalog = loadCatalog(), legacy, home, { semBa
152
167
  stale,
153
168
  adotada,
154
169
  registrarHooks: adotada && !pluginHooks,
155
- registrarComentarios: adotada,
170
+ registrarComentarios,
171
+ registrarHookComentarios,
172
+ recusarComentarios: semComentarios && config.comentarios.instalar,
156
173
  permissaoAusente: permissoesFaltando.length > 0,
157
174
  permissoesFaltando,
158
175
  permissoesLegadas,
@@ -238,6 +255,10 @@ export function applyUpdate(root, report, options, pkg, now = new Date()) {
238
255
  gravarRegistrarMcp(root, false);
239
256
  ajustes.push(`banco_dados.registrar_mcp: false gravado em ${CONFIG_PATH}.`);
240
257
  }
258
+ if (report.recusarComentarios) {
259
+ gravarInstalarComentarios(root, false);
260
+ ajustes.push(`comentarios.instalar: false gravado em ${CONFIG_PATH}.`);
261
+ }
241
262
  const itemOf = new Map(report.changes.map((change) => [change.caminho, change.item]));
242
263
  const writtenHash = new Map(write.map((file) => [file.path, hashContent(file.content)]));
243
264
  const dropped = new Set([...remove, ...report.stale, ...legacy]);
@@ -270,6 +291,10 @@ export function applyUpdate(root, report, options, pkg, now = new Date()) {
270
291
  for (const added of addedItems.values()) {
271
292
  itens.push({ ...added, arquivos: added.arquivos.sort((a, b) => a.caminho.localeCompare(b.caminho)) });
272
293
  }
294
+ // Com o hash do template, o arquivo do projeto aparece como modificado-local e nunca é regravado sem --forcar.
295
+ if (report.registrarHookComentarios) {
296
+ itens.push({ tipo: 'hook', id: 'comentarios', arquivos: [{ caminho: COMMENTS_HOOK_PATH, sha256: hashContent(readFileSync(COMMENTS_HOOK_TEMPLATE)) }] });
297
+ }
273
298
  writeLock(root, { lock_versao: LOCK_VERSION, pacote: pkg, gerado_em: now.toISOString(), itens });
274
299
  return { written: write.map((file) => file.path), removed: remove, kept, renamed, avisos, ajustes };
275
300
  }
@@ -294,7 +319,7 @@ export function describeUpdate(report) {
294
319
  const adopted = report.adotada
295
320
  ? [
296
321
  `Instalação ${report.lock.pacote.versao} sem lock, reconhecida pelos arquivos em .claude/. ` +
297
- `O --yes grava o ${LOCK_PATH}, a configuração do harness${report.registrarHooks ? ', os hooks' : ''}, o padrão de comentários, ` +
322
+ `O --yes grava o ${LOCK_PATH}, a configuração do harness${report.registrarHooks ? ', os hooks' : ''}${report.registrarComentarios ? ', o padrão de comentários' : ''}, ` +
298
323
  'a rule de projeto e a permissão do `ai-harness evento`.',
299
324
  ]
300
325
  : [];
@@ -303,6 +328,21 @@ export function describeUpdate(report) {
303
328
  `Faltam permissões do harness em .claude/settings.json (${report.permissoesFaltando.join(', ')}); o --yes as acrescenta.`,
304
329
  ]
305
330
  : [];
331
+ const comentarios = [
332
+ ...(report.registrarComentarios && !report.adotada
333
+ ? [
334
+ 'O padrão de comentários não está instalado: o --yes grava a rule e o hook, registra o hook em .claude/settings.json e gera a catraca do legado. ' +
335
+ `Para recusar, rode com --sem-comentarios, que grava comentarios.instalar: false em ${CONFIG_PATH}.`,
336
+ ]
337
+ : []),
338
+ ...(report.registrarHookComentarios
339
+ ? [
340
+ `O hook de comentários ${COMMENTS_HOOK_PATH} é do projeto (instalado por versão antiga ou adaptado) e não está no lock: ` +
341
+ 'o --yes o deixa registrado no lock como alterado localmente, sem tocar no arquivo.',
342
+ ]
343
+ : []),
344
+ ...(report.recusarComentarios ? [`--sem-comentarios: o --yes grava comentarios.instalar: false em ${CONFIG_PATH} e não instala o padrão de comentários.`] : []),
345
+ ];
306
346
  const legadas = report.permissoesLegadas.length
307
347
  ? [`Permissões do MCP de banco com o nome antigo em .claude/settings.json (${report.permissoesLegadas.join(', ')}); o --yes as remove.`]
308
348
  : [];
@@ -316,11 +356,14 @@ export function describeUpdate(report) {
316
356
  ...(report.recusarMcp ? [`--sem-banco-dados: o --yes grava banco_dados.registrar_mcp: false em ${CONFIG_PATH} e não registra o MCP.`] : []),
317
357
  ];
318
358
  if (!report.changes.length) {
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');
359
+ const status = adopted.length || comentarios.length || permission.length || legadas.length || mcp.length
360
+ ? 'Nenhum arquivo a atualizar.'
361
+ : 'A instalação está em dia: nenhum arquivo a atualizar.';
362
+ return [...adopted, ...comentarios, ...permission, ...legadas, ...mcp, status].join('\n');
321
363
  }
322
364
  return [
323
365
  ...adopted,
366
+ ...comentarios,
324
367
  ...permission,
325
368
  ...legadas,
326
369
  ...mcp,
@@ -328,9 +371,9 @@ export function describeUpdate(report) {
328
371
  ...report.changes.map((change) => ` ${change.tipo.padEnd(16)} ${change.caminho}${change.destino ? ` → ${change.destino}` : ''} (${DESCRIPTIONS[change.tipo]})`),
329
372
  ].join('\n');
330
373
  }
331
- const UPDATE_FLAGS = ['--check', '--yes', '-y', '--sim', '--forcar', '--sem-banco-dados'];
374
+ const UPDATE_FLAGS = ['--check', '--yes', '-y', '--sim', '--forcar', '--sem-banco-dados', '--sem-comentarios'];
332
375
  export function parseUpdateArgs(argv) {
333
- const args = { path: undefined, check: false, yes: false, force: false, semBanco: false };
376
+ const args = { path: undefined, check: false, yes: false, force: false, semBanco: false, semComentarios: false };
334
377
  for (const token of argv) {
335
378
  if (!token.startsWith('-')) {
336
379
  if (args.path !== undefined)
@@ -343,6 +386,8 @@ export function parseUpdateArgs(argv) {
343
386
  args.force = true;
344
387
  else if (token === '--sem-banco-dados')
345
388
  args.semBanco = true;
389
+ else if (token === '--sem-comentarios')
390
+ args.semComentarios = true;
346
391
  else if (token === '--yes' || token === '-y' || token === '--sim')
347
392
  args.yes = true;
348
393
  else {
@@ -352,16 +397,16 @@ export function parseUpdateArgs(argv) {
352
397
  }
353
398
  return args;
354
399
  }
355
- /** `ai-harness update [caminho] [--check] [--yes] [--forcar] [--sem-banco-dados]`; sem `--yes` só simula. */
400
+ /** `ai-harness update [caminho] [--check] [--yes] [--forcar] [--sem-banco-dados] [--sem-comentarios]`; sem `--yes` só simula. */
356
401
  export function runUpdateCommand(argv) {
357
402
  const args = parseUpdateArgs(argv);
358
403
  const root = resolve(process.cwd(), args.path ?? '.');
359
404
  if (!existsSync(root))
360
405
  throw new Error(`Pasta não encontrada: ${root}`);
361
- const report = checkUpdate(root, undefined, undefined, undefined, { semBanco: args.semBanco });
406
+ const report = checkUpdate(root, undefined, undefined, undefined, { semBanco: args.semBanco, semComentarios: args.semComentarios });
362
407
  console.log(describeUpdate(report));
363
408
  // Sem arquivo a atualizar, a adoção ainda grava o lock, e a permissão e o MCP ainda são acrescentados.
364
- const pendente = report.adotada || report.permissaoAusente || report.permissoesLegadas.length > 0 || report.mudancaMcp !== null || report.recusarMcp || report.renomearSecaoBanco;
409
+ const pendente = report.adotada || report.permissaoAusente || report.permissoesLegadas.length > 0 || report.mudancaMcp !== null || report.recusarMcp || report.recusarComentarios || report.registrarHookComentarios || report.renomearSecaoBanco;
365
410
  if (args.check || (!report.changes.length && !pendente))
366
411
  return 0;
367
412
  if (!args.yes) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fontdata/ai-harness",
3
- "version": "3.2.1",
3
+ "version": "3.2.3",
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": {