@fontdata/ai-harness 3.2.2 → 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.2",
3
+ "version": "3.2.3",
4
4
  "mcpServers": {
5
- "banco_dados": { "command": "npx", "args": ["-y", "@fontdata/ai-harness@3.2.2", "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
@@ -249,7 +249,8 @@ também acionado pelo modelo quando o pedido combina.
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
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. Para recusar, use `--sem-comentarios` no `init` ou
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
253
254
  no `update`: a escolha fica em `comentarios.instalar: false` no `.ai-harness/config.json`, e o `update` a respeita.
254
255
  Para reativar, apague a chave e rode `update --yes` (ou `npx @fontdata/ai-harness comentarios --yes`). Sem o padrão
255
256
  e sem a recusa, o `doctor` avisa com `comentarios-ausente`.
@@ -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,9 +391,14 @@ function checkOldCommentsBlock(root) {
390
391
  },
391
392
  ];
392
393
  }
393
- /** Lock anterior ao padrão de comentários e sem a recusa no config: o update o instala. */
394
- function checkComments(lock, config) {
395
- if (!config.comentarios.instalar || lock.itens.some((item) => item.tipo === 'hook' && item.id === 'comentarios'))
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'))
396
402
  return [];
397
403
  return [
398
404
  {
@@ -520,7 +526,7 @@ export function runDoctor(root, options = {}) {
520
526
  return [
521
527
  ...checkNode(options.nodeVersion ?? process.versions.node),
522
528
  ...lockFindings,
523
- ...(lock ? [...checkFiles(root, lock), ...checkRequiredSkills(root, lock), ...checkComments(lock, config)] : []),
529
+ ...(lock ? [...checkFiles(root, lock), ...checkRequiredSkills(root, lock), ...checkComments(root, lock, config)] : []),
524
530
  ...checkShadowedCommands(root),
525
531
  ...checkOldCommentsBlock(root),
526
532
  ...checkAgents(root),
@@ -1,7 +1,7 @@
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';
3
+ import { allCommands, loadCatalog, templatesDir } from '../catalog.js';
4
+ import { COMMENTS_HOOK_PATH, commentsHooksConfig, ensureCommentsBaseline } from '../comentarios/instalar.js';
5
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';
@@ -50,6 +50,12 @@ function legacyPath(root, path) {
50
50
  return candidate;
51
51
  }
52
52
  }
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
+ }
53
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);
@@ -139,7 +145,10 @@ export function checkUpdate(root, catalog = loadCatalog(), legacy, home, { semBa
139
145
  }
140
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
141
147
  // referência: o comando `comentarios` não registra a rule quando o projeto já tem uma própria.
142
- const registrarComentarios = !semComentarios && config.comentarios.instalar && (adotada || !trackedItems.has('hook:comentarios'));
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);
143
152
  if (registrarComentarios) {
144
153
  addItem('rule', 'comentarios', true);
145
154
  addItem('hook', 'comentarios', true);
@@ -159,6 +168,7 @@ export function checkUpdate(root, catalog = loadCatalog(), legacy, home, { semBa
159
168
  adotada,
160
169
  registrarHooks: adotada && !pluginHooks,
161
170
  registrarComentarios,
171
+ registrarHookComentarios,
162
172
  recusarComentarios: semComentarios && config.comentarios.instalar,
163
173
  permissaoAusente: permissoesFaltando.length > 0,
164
174
  permissoesFaltando,
@@ -281,6 +291,10 @@ export function applyUpdate(root, report, options, pkg, now = new Date()) {
281
291
  for (const added of addedItems.values()) {
282
292
  itens.push({ ...added, arquivos: added.arquivos.sort((a, b) => a.caminho.localeCompare(b.caminho)) });
283
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
+ }
284
298
  writeLock(root, { lock_versao: LOCK_VERSION, pacote: pkg, gerado_em: now.toISOString(), itens });
285
299
  return { written: write.map((file) => file.path), removed: remove, kept, renamed, avisos, ajustes };
286
300
  }
@@ -321,6 +335,12 @@ export function describeUpdate(report) {
321
335
  `Para recusar, rode com --sem-comentarios, que grava comentarios.instalar: false em ${CONFIG_PATH}.`,
322
336
  ]
323
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
+ : []),
324
344
  ...(report.recusarComentarios ? [`--sem-comentarios: o --yes grava comentarios.instalar: false em ${CONFIG_PATH} e não instala o padrão de comentários.`] : []),
325
345
  ];
326
346
  const legadas = report.permissoesLegadas.length
@@ -386,7 +406,7 @@ export function runUpdateCommand(argv) {
386
406
  const report = checkUpdate(root, undefined, undefined, undefined, { semBanco: args.semBanco, semComentarios: args.semComentarios });
387
407
  console.log(describeUpdate(report));
388
408
  // Sem arquivo a atualizar, a adoção ainda grava o lock, e a permissão e o MCP ainda são acrescentados.
389
- const pendente = report.adotada || report.permissaoAusente || report.permissoesLegadas.length > 0 || report.mudancaMcp !== null || report.recusarMcp || report.recusarComentarios || report.renomearSecaoBanco;
409
+ const pendente = report.adotada || report.permissaoAusente || report.permissoesLegadas.length > 0 || report.mudancaMcp !== null || report.recusarMcp || report.recusarComentarios || report.registrarHookComentarios || report.renomearSecaoBanco;
390
410
  if (args.check || (!report.changes.length && !pendente))
391
411
  return 0;
392
412
  if (!args.yes) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fontdata/ai-harness",
3
- "version": "3.2.2",
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": {