@fontdata/ai-harness 2.0.0 → 2.2.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.
Files changed (132) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +9 -2
  3. package/LICENSE +21 -0
  4. package/README.md +175 -140
  5. package/{src → dist}/catalog.js +5 -12
  6. package/dist/cli.js +180 -0
  7. package/dist/comentarios/command.js +74 -0
  8. package/dist/comentarios/instalar.js +33 -0
  9. package/dist/config/config.js +99 -0
  10. package/dist/contratos/artefatos.js +284 -0
  11. package/dist/contratos/eventos.js +103 -0
  12. package/dist/doctor/checks.js +424 -0
  13. package/dist/init.js +240 -0
  14. package/dist/install/atomic.js +51 -0
  15. package/dist/install/flags.js +99 -0
  16. package/dist/install/hooks.js +47 -0
  17. package/dist/install/lock.js +39 -0
  18. package/dist/install/plan.js +209 -0
  19. package/dist/install/selection.js +77 -0
  20. package/dist/install/settings.js +112 -0
  21. package/dist/memoria/indice.js +195 -0
  22. package/dist/migrar/migrar.js +426 -0
  23. package/dist/rastro/command.js +46 -0
  24. package/dist/run.js +86 -0
  25. package/dist/trabalhos/conflitos.js +115 -0
  26. package/dist/types.js +1 -0
  27. package/dist/update/legado.js +74 -0
  28. package/dist/update/update.js +284 -0
  29. package/package.json +17 -8
  30. package/templates/CLAUDE.md +59 -30
  31. package/templates/agents/auditor-plano.md +44 -0
  32. package/templates/agents/investigador.md +32 -0
  33. package/templates/agents/qa.md +44 -0
  34. package/templates/agents/revisor-testes.md +40 -0
  35. package/templates/catalog.json +24 -61
  36. package/templates/commands/atualizar-deps.md +43 -9
  37. package/templates/commands/diagrama.md +35 -10
  38. package/templates/commands/gerar-env.md +39 -10
  39. package/templates/commands/gerar-testes.md +42 -10
  40. package/templates/commands/git-commit-push.md +0 -0
  41. package/templates/commands/onboarding.md +44 -15
  42. package/templates/commands/refatorar.md +50 -10
  43. package/templates/commands/revisar-pr.md +56 -12
  44. package/templates/commands/sincronizar-claude-md.md +177 -9
  45. package/templates/commands/versao.md +0 -0
  46. package/templates/hooks/comentarios/check.mjs +210 -0
  47. package/templates/hooks/hooks.json +34 -0
  48. package/templates/hooks/lembrete.mjs +29 -0
  49. package/templates/hooks/lib/config.mjs +40 -0
  50. package/templates/hooks/lib/hook.mjs +85 -0
  51. package/templates/hooks/lib/rastro.mjs +144 -0
  52. package/templates/hooks/lib/segredos.mjs +91 -0
  53. package/templates/hooks/lib/temas.mjs +111 -0
  54. package/templates/hooks/lib/trabalho.mjs +94 -0
  55. package/templates/hooks/proteger-contratos.mjs +80 -0
  56. package/templates/hooks/segredo-no-commit.mjs +76 -0
  57. package/templates/hooks/sem-jargao.mjs +58 -0
  58. package/templates/hooks/task-so-fecha-verde.mjs +33 -0
  59. package/templates/legado/manifesto.json +1267 -0
  60. package/templates/rules/comentarios.md +166 -0
  61. package/templates/rules/harness-projeto.md +41 -0
  62. package/templates/skills/auditar-performance/SKILL.md +53 -0
  63. package/templates/skills/auditar-performance/references/pontos-quentes.md +35 -0
  64. package/templates/skills/auditar-performance/templates/relatorios/CLIENTE.md +26 -0
  65. package/templates/skills/auditar-performance/templates/relatorios/TECNICO.md +27 -0
  66. package/templates/skills/auditar-seguranca/SKILL.md +67 -0
  67. package/templates/skills/auditar-seguranca/references/seguranca-tecnica.md +23 -0
  68. package/templates/skills/auditar-seguranca/templates/relatorios/CLIENTE.md +30 -0
  69. package/templates/skills/auditar-seguranca/templates/relatorios/TECNICO.md +38 -0
  70. package/templates/skills/auditar-ui/SKILL.md +64 -0
  71. package/templates/skills/auditar-ui/references/lentes.md +33 -0
  72. package/templates/skills/auditar-ui/templates/relatorios/CLIENTE.md +26 -0
  73. package/templates/skills/auditar-ui/templates/relatorios/TECNICO.md +31 -0
  74. package/templates/skills/evoluir-harness/SKILL.md +81 -0
  75. package/templates/skills/evoluir-harness/references/agent.md +83 -0
  76. package/templates/skills/evoluir-harness/references/auditoria.md +107 -0
  77. package/templates/skills/evoluir-harness/references/code-reviewer.md +68 -0
  78. package/templates/skills/evoluir-harness/references/design-system.md +56 -0
  79. package/templates/skills/evoluir-harness/references/hook.md +95 -0
  80. package/templates/skills/evoluir-harness/references/rule.md +67 -0
  81. package/templates/skills/evoluir-harness/references/skill.md +63 -0
  82. package/templates/skills/executar-plan/SKILL.md +130 -0
  83. package/templates/skills/executar-plan/references/delegacao.md +43 -0
  84. package/templates/skills/executar-plan/references/retomada.md +33 -0
  85. package/templates/skills/executar-plan/templates/BLOQUEIOS.md +30 -0
  86. package/templates/skills/harness/SKILL.md +75 -0
  87. package/templates/skills/harness/references/auto-tag.md +118 -0
  88. package/templates/skills/harness/references/ignores.md +40 -0
  89. package/templates/skills/harness/references/scaffold.md +29 -0
  90. package/templates/skills/harness/references/validar-ideia.md +42 -0
  91. package/templates/skills/memoria/SKILL.md +52 -0
  92. package/templates/skills/nova-tarefa/SKILL.md +99 -0
  93. package/templates/skills/nova-tarefa/references/isso-ja-existe.md +24 -0
  94. package/templates/skills/nova-tarefa/references/regras-do-plano.md +35 -0
  95. package/templates/skills/nova-tarefa/templates/DECISOES.md +20 -0
  96. package/templates/skills/nova-tarefa/templates/PLAN.md +58 -0
  97. package/templates/skills/nova-tarefa/templates/SPEC.md +37 -0
  98. package/templates/skills/resolver-bug/SKILL.md +104 -0
  99. package/templates/skills/resolver-bug/references/relatorio-para-cliente.md +26 -0
  100. package/templates/skills/resolver-bug/templates/PLAN.md +62 -0
  101. package/templates/skills/resolver-bug/templates/relatorios/CLIENTE.md +29 -0
  102. package/templates/skills/resolver-bug/templates/relatorios/TECNICO.md +32 -0
  103. package/bin/cli.js +0 -122
  104. package/src/init.js +0 -317
  105. package/src/installer.js +0 -92
  106. package/src/run.js +0 -99
  107. package/templates/commands/auditar-performance.md +0 -27
  108. package/templates/commands/auditar-seguranca.md +0 -64
  109. package/templates/commands/auditar-ui.md +0 -38
  110. package/templates/commands/codigo-morto.md +0 -22
  111. package/templates/commands/configurar-auto-tag.md +0 -185
  112. package/templates/commands/configurar-comentarios.md +0 -543
  113. package/templates/commands/configurar-orquestrador.md +0 -13
  114. package/templates/commands/corrigir-review.md +0 -11
  115. package/templates/commands/criar-agents.md +0 -42
  116. package/templates/commands/criar-code-reviewer.md +0 -30
  117. package/templates/commands/criar-hooks.md +0 -32
  118. package/templates/commands/criar-rag.md +0 -25
  119. package/templates/commands/criar-rules.md +0 -31
  120. package/templates/commands/criar-skill.md +0 -22
  121. package/templates/commands/design-system.md +0 -27
  122. package/templates/commands/executar-plan.md +0 -36
  123. package/templates/commands/gerar-claude-md.md +0 -66
  124. package/templates/commands/gerar-ignore.md +0 -37
  125. package/templates/commands/gerar-spec.md +0 -33
  126. package/templates/commands/harness.md +0 -34
  127. package/templates/commands/nova-tarefa.md +0 -60
  128. package/templates/commands/resolver-bug.md +0 -25
  129. package/templates/commands/review-por-task.md +0 -16
  130. package/templates/commands/scaffold.md +0 -25
  131. package/templates/commands/validar-commands.md +0 -24
  132. package/templates/commands/validar-ideia.md +0 -36
@@ -0,0 +1,74 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { templatesDir } from '../catalog.js';
4
+ import { hashContent, LOCK_VERSION } from '../install/lock.js';
5
+ /**
6
+ * Rules eram transcritas pelo modelo a partir do bloco do comando e, em vários projetos, passaram depois por um
7
+ * formatador. Por isso o manifesto guarda, para elas, o hash do texto normalizado: sem linhas em branco, sem espaço
8
+ * no fim das linhas e com as tabelas reduzidas ao conteúdo das células.
9
+ */
10
+ export function normalizeText(text) {
11
+ const lines = text
12
+ .replace(/\r\n/g, '\n')
13
+ .split('\n')
14
+ .map((line) => line.replace(/\s+$/, ''))
15
+ .filter((line) => line !== '')
16
+ .map((line) => {
17
+ if (!line.trimStart().startsWith('|'))
18
+ return line;
19
+ const cells = line.split('|').map((cell) => cell.trim());
20
+ return cells.map((cell) => (/^:?-{3,}:?$/.test(cell) ? cell.replace(/-+/, '---') : cell)).join(' | ').trim();
21
+ });
22
+ return `${lines.join('\n')}\n`;
23
+ }
24
+ export function loadLegacyManifest() {
25
+ return JSON.parse(readFileSync(join(templatesDir, 'legado', 'manifesto.json'), 'utf8'));
26
+ }
27
+ /**
28
+ * Reconhece uma instalação anterior à 2.1, que não gravava lock, pelos caminhos que as versões
29
+ * publicadas instalavam, e monta o lock que o update compara em três pontas. Arquivo igual a
30
+ * qualquer versão publicada é intacto e entra com o próprio hash; arquivo editado entra com o
31
+ * hash da versão mais recente, para o update enxergar a alteração local. O CLAUDE.md só conta
32
+ * quando é igual a uma versão publicada, porque o nome não é exclusivo do harness.
33
+ */
34
+ export function adoptLegacyInstall(root, manifest = loadLegacyManifest()) {
35
+ const order = (version) => manifest.versoes.indexOf(version);
36
+ const itens = [];
37
+ let newest = -1;
38
+ for (const item of manifest.itens) {
39
+ const arquivos = [];
40
+ for (const entry of item.arquivos) {
41
+ const local = join(root, entry.caminho);
42
+ if (!existsSync(local))
43
+ continue;
44
+ const content = readFileSync(local);
45
+ const hash = hashContent(content);
46
+ const versions = entry.hashes[hash] ?? (item.tipo === 'rule' ? entry.hashes[hashContent(normalizeText(content.toString('utf8')))] : undefined);
47
+ if (versions) {
48
+ newest = Math.max(newest, ...versions.map(order));
49
+ arquivos.push({ caminho: entry.caminho, sha256: hash });
50
+ }
51
+ else if (item.tipo !== 'claude-md') {
52
+ arquivos.push({ caminho: entry.caminho, sha256: latestHash(entry.hashes, order) });
53
+ }
54
+ }
55
+ if (arquivos.length)
56
+ itens.push({ tipo: item.tipo, id: item.id, arquivos });
57
+ }
58
+ if (!itens.length)
59
+ return null;
60
+ const versao = manifest.versoes[newest] ?? manifest.versoes.at(-1) ?? '2.0.0';
61
+ return { lock_versao: LOCK_VERSION, pacote: { nome: '@fontdata/ai-harness', versao }, gerado_em: '', itens };
62
+ }
63
+ function latestHash(hashes, order) {
64
+ let latest = '';
65
+ let latestOrder = -Infinity;
66
+ for (const [hash, versions] of Object.entries(hashes)) {
67
+ const top = Math.max(...versions.map(order));
68
+ if (top > latestOrder) {
69
+ latest = hash;
70
+ latestOrder = top;
71
+ }
72
+ }
73
+ return latest;
74
+ }
@@ -0,0 +1,284 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
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 { readConfig, syncGitignore, writeDefaultConfig } from '../config/config.js';
6
+ import { applyAtomically } from '../install/atomic.js';
7
+ import { suggestFlag } from '../install/flags.js';
8
+ import { HOOKS_ITEM_ID, isHarnessPluginEnabled, projectHooksConfig } from '../install/hooks.js';
9
+ import { hashContent, LOCK_PATH, LOCK_VERSION, readLock, writeLock } from '../install/lock.js';
10
+ import { itemSources, packageInfo, PROJECT_RULE_PATH, projectRuleContent } from '../install/plan.js';
11
+ import { neededSkillsFor } from '../install/selection.js';
12
+ import { combineHooks, HARNESS_PERMISSIONS, hasAllowRule, mergeSettings } from '../install/settings.js';
13
+ import { adoptLegacyInstall } from './legado.js';
14
+ /** `<id>-legado.md`, com sufixo numérico quando o nome já está em uso. */
15
+ function legacyPath(root, path) {
16
+ const base = path.replace(/\.md$/, '');
17
+ for (let n = 1;; n += 1) {
18
+ const candidate = `${base}-legado${n === 1 ? '' : `-${n}`}.md`;
19
+ if (!existsSync(join(root, candidate)))
20
+ return candidate;
21
+ }
22
+ }
23
+ export function checkUpdate(root, catalog = loadCatalog(), legacy, home) {
24
+ const recorded = readLock(root);
25
+ const lock = recorded ?? adoptLegacyInstall(root, legacy);
26
+ if (!lock) {
27
+ throw new Error('Nenhuma instalação do ai-harness registrada neste projeto. Rode `npx @fontdata/ai-harness init` primeiro.');
28
+ }
29
+ const changes = [];
30
+ const stale = [];
31
+ for (const item of lock.itens) {
32
+ const key = `${item.tipo}:${item.id}`;
33
+ const sources = itemSources(catalog, item.tipo, item.id) ?? [];
34
+ const sourceByPath = new Map(sources.map((entry) => [entry.path, entry.source]));
35
+ for (const entry of item.arquivos) {
36
+ const local = join(root, entry.caminho);
37
+ const exists = existsSync(local);
38
+ const localChanged = exists && hashContent(readFileSync(local)) !== entry.sha256;
39
+ const source = sourceByPath.get(entry.caminho) ?? null;
40
+ if (!source) {
41
+ if (!exists)
42
+ stale.push(entry.caminho);
43
+ else if (!localChanged)
44
+ changes.push({ tipo: 'removido', caminho: entry.caminho, item: key, source });
45
+ // Comando editado que saiu do pacote: renomeado, para não sombrear a skill de mesmo nome (D-35).
46
+ else if (item.tipo === 'comando') {
47
+ changes.push({ tipo: 'legado', caminho: entry.caminho, item: key, source, destino: legacyPath(root, entry.caminho) });
48
+ }
49
+ else
50
+ changes.push({ tipo: 'conflito', caminho: entry.caminho, item: key, source });
51
+ continue;
52
+ }
53
+ const packageChanged = hashContent(readFileSync(source)) !== entry.sha256;
54
+ const tipo = !exists
55
+ ? 'faltando'
56
+ : packageChanged && localChanged
57
+ ? 'conflito'
58
+ : packageChanged
59
+ ? 'alterado'
60
+ : localChanged
61
+ ? 'modificado-local'
62
+ : null;
63
+ if (tipo)
64
+ changes.push({ tipo, caminho: entry.caminho, item: key, source });
65
+ }
66
+ const trackedPaths = new Set(item.arquivos.map((entry) => entry.caminho));
67
+ for (const { path, source } of sources) {
68
+ if (trackedPaths.has(path))
69
+ continue;
70
+ changes.push({ tipo: existsSync(join(root, path)) ? 'conflito' : 'novo', caminho: path, item: key, source });
71
+ }
72
+ }
73
+ // Itens que o lock não tem. Arquivo igual ao do pacote só entra no lock; `protect` mantém o do projeto mesmo com --forcar.
74
+ const trackedItems = new Set(lock.itens.map((item) => `${item.tipo}:${item.id}`));
75
+ const addItem = (tipo, id, protect) => {
76
+ const key = `${tipo}:${id}`;
77
+ if (trackedItems.has(key))
78
+ return;
79
+ trackedItems.add(key);
80
+ for (const { path, source } of itemSources(catalog, tipo, id) ?? []) {
81
+ const target = join(root, path);
82
+ const same = existsSync(target) && hashContent(readFileSync(target)) === hashContent(readFileSync(source));
83
+ const tipoMudanca = !existsSync(target) || same ? 'novo' : protect ? 'existente' : 'conflito';
84
+ changes.push({ tipo: tipoMudanca, caminho: path, item: key, source });
85
+ }
86
+ };
87
+ // Comando que virou skill, ou que foi incorporado a outro item (D-40 a D-43): quem o substitui entra como item
88
+ // novo, com as skills que exige.
89
+ const entries = [...allCommands(catalog), { ...catalog.master, group: '' }];
90
+ for (const item of lock.itens) {
91
+ if (item.tipo !== 'comando')
92
+ continue;
93
+ const successors = entries.filter((cmd) => (cmd.id === item.id && cmd.skill) || cmd.replaces?.includes(item.id));
94
+ for (const successor of successors) {
95
+ addItem(successor.skill ? 'skill' : 'comando', successor.id, false);
96
+ for (const skill of neededSkillsFor(catalog, [successor]))
97
+ addItem('skill', skill.id, false);
98
+ }
99
+ }
100
+ // Na adoção, a instalação antiga ganha o que a 2.1 instala e ela não tinha.
101
+ const adotada = !recorded;
102
+ const pluginHooks = adotada && isHarnessPluginEnabled(root, home);
103
+ if (adotada) {
104
+ for (const agent of catalog.agents ?? [])
105
+ addItem('agent', agent.id, true);
106
+ if (!pluginHooks)
107
+ addItem('hook', HOOKS_ITEM_ID, true);
108
+ addItem('rule', 'comentarios', true);
109
+ addItem('hook', 'comentarios', true);
110
+ }
111
+ return {
112
+ changes,
113
+ lock,
114
+ stale,
115
+ adotada,
116
+ registrarHooks: adotada && !pluginHooks,
117
+ registrarComentarios: adotada,
118
+ permissaoAusente: !HARNESS_PERMISSIONS.every((rule) => hasAllowRule(root, rule)),
119
+ };
120
+ }
121
+ /**
122
+ * Aplica o relatório: grava novos, alterados e faltando; apaga órfãos intactos; renomeia
123
+ * comandos editados que saíram do pacote; conflitos só com `force`. Arquivos modificados
124
+ * localmente sem mudança no pacote e arquivos do projeto com nome de item do harness nunca
125
+ * são tocados. O lock é regravado com a versão atual do pacote.
126
+ */
127
+ export function applyUpdate(root, report, options, pkg, now = new Date()) {
128
+ const write = [];
129
+ const remove = [];
130
+ const kept = [];
131
+ const legacy = [];
132
+ for (const change of report.changes) {
133
+ if (change.tipo === 'modificado-local')
134
+ continue;
135
+ if (change.tipo === 'legado') {
136
+ legacy.push(change.caminho);
137
+ continue;
138
+ }
139
+ if (change.tipo === 'existente' || (change.tipo === 'conflito' && !options.force)) {
140
+ kept.push(change.caminho);
141
+ continue;
142
+ }
143
+ if (change.source)
144
+ write.push({ path: change.caminho, content: readFileSync(change.source) });
145
+ else
146
+ remove.push(change.caminho);
147
+ }
148
+ applyAtomically(root, write);
149
+ for (const path of remove)
150
+ rmSync(join(root, path), { force: true });
151
+ const renamed = legacy.map((path) => {
152
+ const para = legacyPath(root, path);
153
+ renameSync(join(root, path), join(root, para));
154
+ return { de: path, para };
155
+ });
156
+ const hooks = combineHooks(report.registrarHooks ? projectHooksConfig() : {}, report.registrarComentarios ? commentsHooksConfig() : {});
157
+ mergeSettings(root, { hooks, allow: HARNESS_PERMISSIONS }, now);
158
+ if (report.registrarComentarios)
159
+ ensureCommentsBaseline(root);
160
+ if (report.adotada) {
161
+ writeDefaultConfig(root);
162
+ syncGitignore(root, readConfig(root).config);
163
+ // Esqueleto da rule de projeto, com os comandos personalizados a extrair; uma rule existente nunca é tocada.
164
+ const projectRule = join(root, PROJECT_RULE_PATH);
165
+ if (!existsSync(projectRule)) {
166
+ mkdirSync(dirname(projectRule), { recursive: true });
167
+ writeFileSync(projectRule, projectRuleContent(renamed.map(({ para }) => para)));
168
+ }
169
+ }
170
+ const itemOf = new Map(report.changes.map((change) => [change.caminho, change.item]));
171
+ const writtenHash = new Map(write.map((file) => [file.path, hashContent(file.content)]));
172
+ const dropped = new Set([...remove, ...report.stale, ...legacy]);
173
+ const itens = [];
174
+ for (const item of report.lock.itens) {
175
+ const key = `${item.tipo}:${item.id}`;
176
+ const entries = new Map(item.arquivos.map((entry) => [entry.caminho, entry]));
177
+ for (const [path, sha256] of writtenHash) {
178
+ if (itemOf.get(path) === key)
179
+ entries.set(path, { caminho: path, sha256 });
180
+ }
181
+ for (const path of dropped)
182
+ entries.delete(path);
183
+ if (entries.size) {
184
+ itens.push({ ...item, arquivos: [...entries.values()].sort((a, b) => a.caminho.localeCompare(b.caminho)) });
185
+ }
186
+ }
187
+ // Itens que não existiam no lock (como a skill de um fluxo que era comando) entram com os arquivos gravados.
188
+ const knownItems = new Set(report.lock.itens.map((item) => `${item.tipo}:${item.id}`));
189
+ const addedItems = new Map();
190
+ for (const [path, sha256] of writtenHash) {
191
+ const key = itemOf.get(path);
192
+ if (!key || knownItems.has(key))
193
+ continue;
194
+ const separator = key.indexOf(':');
195
+ const added = addedItems.get(key) ?? { tipo: key.slice(0, separator), id: key.slice(separator + 1), arquivos: [] };
196
+ added.arquivos.push({ caminho: path, sha256 });
197
+ addedItems.set(key, added);
198
+ }
199
+ for (const added of addedItems.values()) {
200
+ itens.push({ ...added, arquivos: added.arquivos.sort((a, b) => a.caminho.localeCompare(b.caminho)) });
201
+ }
202
+ writeLock(root, { lock_versao: LOCK_VERSION, pacote: pkg, gerado_em: now.toISOString(), itens });
203
+ return { written: write.map((file) => file.path), removed: remove, kept, renamed };
204
+ }
205
+ const DESCRIPTIONS = {
206
+ novo: 'novo no pacote, será gravado',
207
+ alterado: 'mudou no pacote, será atualizado',
208
+ faltando: 'não existe no projeto, será restaurado',
209
+ removido: 'saiu do pacote, será apagado',
210
+ legado: 'editado localmente e saiu do pacote, será renomeado',
211
+ conflito: 'alterado localmente e no pacote, só com --forcar',
212
+ existente: 'já existe no projeto com outro conteúdo, será mantido',
213
+ 'modificado-local': 'alterado localmente, será mantido',
214
+ };
215
+ export function describeUpdate(report) {
216
+ const adopted = report.adotada
217
+ ? [
218
+ `Instalação ${report.lock.pacote.versao} sem lock, reconhecida pelos arquivos em .claude/. ` +
219
+ `O --yes grava o ${LOCK_PATH}, a configuração do harness${report.registrarHooks ? ', os hooks' : ''}, o padrão de comentários, ` +
220
+ 'a rule de projeto e a permissão do `ai-harness evento`.',
221
+ ]
222
+ : [];
223
+ const permission = report.permissaoAusente && !report.adotada ? [`Faltam permissões do harness (${HARNESS_PERMISSIONS.join(', ')}) em .claude/settings.json; o --yes as acrescenta.`] : [];
224
+ if (!report.changes.length) {
225
+ const status = adopted.length || permission.length ? 'Nenhum arquivo a atualizar.' : 'A instalação está em dia: nenhum arquivo a atualizar.';
226
+ return [...adopted, ...permission, status].join('\n');
227
+ }
228
+ return [
229
+ ...adopted,
230
+ ...permission,
231
+ `Mudanças em relação ao pacote ${packageInfo().versao} (instalado: ${report.lock.pacote.versao}):`,
232
+ ...report.changes.map((change) => ` ${change.tipo.padEnd(16)} ${change.caminho}${change.destino ? ` → ${change.destino}` : ''} (${DESCRIPTIONS[change.tipo]})`),
233
+ ].join('\n');
234
+ }
235
+ const UPDATE_FLAGS = ['--check', '--yes', '-y', '--sim', '--forcar'];
236
+ export function parseUpdateArgs(argv) {
237
+ const args = { path: undefined, check: false, yes: false, force: false };
238
+ for (const token of argv) {
239
+ if (!token.startsWith('-')) {
240
+ if (args.path !== undefined)
241
+ throw new Error(`Argumento inesperado: ${token}`);
242
+ args.path = token;
243
+ }
244
+ else if (token === '--check')
245
+ args.check = true;
246
+ else if (token === '--forcar')
247
+ args.force = true;
248
+ else if (token === '--yes' || token === '-y' || token === '--sim')
249
+ args.yes = true;
250
+ else {
251
+ const suggestion = suggestFlag(token, UPDATE_FLAGS);
252
+ throw new Error(`Flag desconhecida: ${token}.${suggestion ? ` Você quis dizer ${suggestion}?` : ''}`);
253
+ }
254
+ }
255
+ return args;
256
+ }
257
+ /** `ai-harness update [caminho] [--check] [--yes] [--forcar]`; sem `--yes` só simula. */
258
+ export function runUpdateCommand(argv) {
259
+ const args = parseUpdateArgs(argv);
260
+ const root = resolve(process.cwd(), args.path ?? '.');
261
+ if (!existsSync(root))
262
+ throw new Error(`Pasta não encontrada: ${root}`);
263
+ const report = checkUpdate(root);
264
+ console.log(describeUpdate(report));
265
+ // Sem arquivo a atualizar, a adoção ainda grava o lock, e a permissão ausente ainda é acrescentada.
266
+ if (args.check || (!report.changes.length && !report.adotada && !report.permissaoAusente))
267
+ return 0;
268
+ if (!args.yes) {
269
+ const conflicts = report.changes.some((change) => change.tipo === 'conflito');
270
+ console.log(`\nSimulação — nada foi alterado. Rode de novo com --yes para aplicar${conflicts && !args.force ? ' (e --forcar para sobrescrever os conflitos)' : ''}.`);
271
+ return 0;
272
+ }
273
+ const result = applyUpdate(root, report, { force: args.force }, packageInfo());
274
+ console.log(`\nAtualização aplicada: ${result.written.length} gravado(s), ${result.removed.length} apagado(s), ` +
275
+ `${result.renamed.length} renomeado(s), ${result.kept.length} preservado(s).`);
276
+ if (result.renamed.length) {
277
+ console.log('Comandos personalizados renomeados (leve o que for do projeto para uma rule e apague o arquivo):\n' +
278
+ result.renamed.map(({ de, para }) => ` ${de} → ${para}`).join('\n'));
279
+ }
280
+ if (result.kept.length) {
281
+ console.log(`Preservados (conflito usa --forcar; arquivo do projeto nunca é sobrescrito):\n${result.kept.map((path) => ` = ${path}`).join('\n')}`);
282
+ }
283
+ return 0;
284
+ }
package/package.json CHANGED
@@ -1,27 +1,30 @@
1
1
  {
2
2
  "name": "@fontdata/ai-harness",
3
- "version": "2.0.0",
3
+ "version": "2.2.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": {
7
- "ai-harness": "bin/cli.js",
8
- "fontdata-ai-harness": "bin/cli.js"
7
+ "ai-harness": "dist/cli.js",
8
+ "fontdata-ai-harness": "dist/cli.js"
9
9
  },
10
10
  "engines": {
11
- "node": ">=18"
11
+ "node": ">=22"
12
12
  },
13
13
  "publishConfig": {
14
14
  "access": "public"
15
15
  },
16
16
  "files": [
17
17
  ".claude-plugin/",
18
- "bin/",
19
- "src/",
18
+ "dist/",
20
19
  "templates/",
21
20
  "README.md"
22
21
  ],
23
22
  "scripts": {
24
- "start": "node bin/cli.js",
23
+ "start": "node dist/cli.js",
24
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
25
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.scripts.json",
26
+ "test": "vitest run",
27
+ "prepublishOnly": "npm run typecheck && npm test && npm run build",
25
28
  "version": "node scripts/sync-plugin-version.mjs && git add .claude-plugin/plugin.json"
26
29
  },
27
30
  "keywords": [
@@ -39,6 +42,12 @@
39
42
  "url": "git+https://github.com/fontdata-tecnologia/ai-harness.git"
40
43
  },
41
44
  "dependencies": {
42
- "@clack/prompts": "^0.7.0"
45
+ "@clack/prompts": "^0.7.0",
46
+ "yaml": "^2.9.1"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^22.20.2",
50
+ "typescript": "^7.0.2",
51
+ "vitest": "^5.0.0"
43
52
  }
44
53
  }
@@ -1,49 +1,78 @@
1
1
  # [NOME DO PROJETO]
2
- > Substitua esta seção rodando `/gerar-claude-md` depois de gerar o `SPEC.md`.
2
+ > Substitua esta seção rodando `/sincronizar-claude-md`, que gera o `SPEC.md` e este arquivo.
3
3
 
4
- Este projeto usa o **harness de engenharia com IA da Fontdata** para Claude Code.
5
- Os slash commands ficam em `.claude/commands/`. Comece por `/validar-ideia` (ou `/harness`).
4
+ Este projeto usa o **harness de engenharia com IA da Fontdata** para Claude Code (`@fontdata/ai-harness`).
5
+ Comece por `/harness`, que conduz o setup em etapas.
6
6
 
7
7
  ## Filosofia de trabalho
8
8
 
9
- - **Documentação como contrato.** `SPEC.md` (raiz), `CLAUDE.md` (raiz) e os planos em `docs/plans/`
10
- são a fonte da verdade. Agents não os reescrevem por conta própria.
11
- - **TDD obrigatório.** Toda task escreve os testes primeiro (RED), confirma que falham, e só então
12
- implementa o mínimo para passar (GREEN). Mínimo de 2 testes críticos por task.
13
- - **Orquestração com sub-agents.** Tasks independentes da mesma fase rodam em paralelo. O orquestrador
14
- delega, não implementa. Gates entre fases: só avança quando os testes da fase passam.
15
- - **Sempre relatar qual agent está ativo** no chat, principalmente nos gates entre agents.
9
+ - **Documentação como contrato.** `SPEC.md` e `CLAUDE.md` na raiz e os artefatos de cada trabalho em
10
+ `docs/trabalhos/<id>/` são a fonte da verdade. Agents não os reescrevem por conta própria: mudança de escopo vira
11
+ decisão registrada.
12
+ - **Planejar antes de executar.** Pergunta durante a execução é falha do plano: dúvida vira bloqueio registrado.
13
+ - **TDD obrigatório.** Testes primeiro (RED), implementação mínima depois (GREEN). Mínimo de 2 testes críticos por
14
+ task; uma task só fecha com suíte verde registrada no rastro.
15
+ - **Quem implementa não aprova.** Os agents `revisor-testes` e `qa`, somente leitura, fazem os gates de cada task.
16
+ - **Orquestração com sub-agents.** Tasks independentes da mesma fase rodam em paralelo; o orquestrador delega, não
17
+ implementa. **Sempre relatar qual agent está ativo** no chat.
16
18
 
17
19
  ## Modelo de arquivos
18
20
 
19
- | Arquivo | Local | Papel |
20
- |--------|-------|-------|
21
- | `SPEC.md` | raiz | Definição **principal** e completa do projeto (única). |
22
- | `CLAUDE.md` | raiz | Este context file, lido em toda sessão. |
23
- | `docs/specs/AAAA-MM-DD-*.md` | `docs/specs/` | Specs **complementares**: o start inicial e cada nova tarefa. |
24
- | `docs/plans/AAAA-MM-DD-*.md` | `docs/plans/` | Plano de implementação correspondente a cada spec complementar. |
25
- | `docs/bugs/AAAA-MM-DD-*.md` | `docs/bugs/` | Correção de defeito: sintoma, causa-raiz e fix (via `/resolver-bug`). |
26
- | `docs/investigacoes/AAAA-MM-DD-*.md` | `docs/investigacoes/` | Investigação, medição, auditoria, paridade e comparação (via `/auditar-*`, `/codigo-morto`). |
21
+ | Arquivo | Papel |
22
+ |---|---|
23
+ | `SPEC.md` (raiz) | Definição **principal** e completa do projeto |
24
+ | `CLAUDE.md` (raiz) | Este context file, lido em toda sessão |
25
+ | `docs/trabalhos/AAAA-MM-DD-<slug>/SPEC.md` | Spec do trabalho |
26
+ | `docs/trabalhos/<id>/PLAN.md` | Plano: sprints, fases e tasks (as tasks também no frontmatter) |
27
+ | `docs/trabalhos/<id>/DECISOES.md` | Decisões tomadas no planejamento e na execução |
28
+ | `docs/trabalhos/<id>/BLOQUEIOS.md` | Dúvidas que pararam uma task |
29
+ | `docs/trabalhos/<id>/relatorios/TECNICO.md` e `relatorios/CLIENTE.md` | Relatório para engenharia e para cliente ou gestão |
30
+ | `docs/eventos/<id>.jsonl` | Rastro de eventos (não versionado por padrão) |
31
+ | `.ai-harness/` | Lock da instalação e configuração dos hooks (versionar) |
32
+
33
+ Tipos de trabalho: `feature` (`/nova-tarefa`), `bug` (`/resolver-bug`), `auditoria` (`/auditar-seguranca`,
34
+ `/auditar-performance`, `/auditar-ui`) e `investigacao`.
35
+
36
+ ## Garantias do harness
37
+
38
+ - **Hooks** (modo de cada um em `.ai-harness/config.json`): `lembrete` sugere o fluxo certo para o pedido;
39
+ `segredo-no-commit` bloqueia commit com segredo; `proteger-contratos` avisa quando SPEC, PLAN ou CLAUDE.md são
40
+ reescritos durante a execução; `task-so-fecha-verde` avisa sobre task concluída sem suíte verde; `sem-jargao`
41
+ avisa sobre termo técnico no relatório para o cliente.
42
+ - **Agents somente leitura:** `revisor-testes`, `qa`, `investigador` e `auditor-plano`.
43
+ - **Verificação:** `npx @fontdata/ai-harness doctor` confere instalação, contrato dos documentos e rastro;
44
+ `npx @fontdata/ai-harness update` atualiza o que foi instalado sem perder alterações locais.
45
+
46
+ ## Código legado
47
+
48
+ Na área de código legado tocada por uma task, vale o **padrão local** do arquivo — nomenclatura, estrutura e estilo
49
+ de tratamento de erro —, mesmo que difira das convenções novas. As convenções novas valem para arquivo novo. Não
50
+ "modernize" arquivo antigo fora do escopo da task.
27
51
 
28
52
  ## Fluxo recomendado (projeto novo)
29
53
 
30
- 1. `/validar-ideia` — confere se cabe em ~4h com multi-agents
31
- 2. `/gerar-spec` — gera o `SPEC.md` principal na raiz
32
- 3. `/gerar-claude-md` — gera/atualiza este arquivo
33
- 4. `/gerar-ignore` — `.claudeignore` e `.gitignore`
34
- 5. `/nova-tarefa` — spec do start (derivada do SPEC) + plano em `docs/`
35
- 6. `/scaffold` — estrutura inicial + `/health` + testes de smoke
36
- 7. `/criar-agents`, `/criar-code-reviewer`, `/criar-rules`, `/criar-hooks` — monta o harness
54
+ `/harness` conduz o setup em etapas, pedindo confirmação entre elas (`/harness <etapa>` roda uma só):
55
+
56
+ 1. Validar a ideia — o escopo cabe com multi-agents?
57
+ 2. `/sincronizar-claude-md` — `SPEC.md` principal e este arquivo, com a seção de orquestração
58
+ 3. Ignores — `.claudeignore` e `.gitignore`
59
+ 4. `/nova-tarefa start` — trabalho do start com `SPEC.md` e `PLAN.md`
60
+ 5. Scaffold — estrutura inicial, `/health` e testes de smoke
61
+ 6. `/evoluir-harness` — skills, agents, code-reviewer, rules e hooks do projeto, e a rule
62
+ `.claude/rules/harness-projeto.md` preenchida
63
+ 7. Auto-tag — Action que cria a tag quando a versão chega na branch principal (opcional)
37
64
  8. `/executar-plan` — orquestra a implementação
38
65
 
39
- No dia a dia: `/nova-tarefa`, `/resolver-bug`, `/git-commit-push`.
66
+ No dia a dia: `/nova-tarefa`, `/executar-plan`, `/resolver-bug`, `/git-commit-push` e `/versao`. De vez em quando:
67
+ `/sincronizar-claude-md` e `/evoluir-harness`.
40
68
 
41
69
  ## Nunca fazer
42
70
 
43
71
  - Nunca implementar sem teste escrito antes.
44
- - Nunca avançar de fase com gate (testes) falhando.
45
- - Nunca reescrever `SPEC.md`/`CLAUDE.md`/planos sem pedido explícito.
72
+ - Nunca avançar de fase com gate (testes) falhando nem fechar task sem suíte verde.
73
+ - Nunca reescrever `SPEC.md`, `CLAUDE.md` ou planos sem pedido explícito.
46
74
  - Nunca usar `git push --force` ou comandos destrutivos sem confirmação.
47
75
  - Nunca comentar o passo a passo do código, decorar arquivo com banner de seção, nem escrever
48
- task/fase/plano/data/hash de commit dentro de comentário — isso vai na mensagem de commit e em `docs/`.
49
- Rode `/configurar-comentarios` para instalar a rule, o hook e a catraca.
76
+ task/fase/plano/data/hash de commit dentro de comentário — isso vai na mensagem de commit e em `docs/trabalhos/`.
77
+ O padrão completo está em `.claude/rules/comentarios.md`, instalada pelo `init`, e o hook de comentários bloqueia
78
+ a violação.
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: auditor-plano
3
+ description: Audita um PLAN.md antes da execução, verificando dependências, paralelismo, testes críticos e critérios de aceite binários. Use depois do /nova-tarefa e antes do /executar-plan. Somente leitura.
4
+ tools: Read, Grep, Glob, Bash
5
+ model: opus
6
+ ---
7
+
8
+ Você é o **auditor de plano** do harness. Um plano aprovado por você precisa poder ser executado sem que
9
+ ninguém pergunte nada no meio. Você não edita o plano: aponta o que precisa mudar.
10
+
11
+ ## Entrada
12
+
13
+ - `docs/trabalhos/<id>/SPEC.md` e `docs/trabalhos/<id>/PLAN.md`.
14
+
15
+ ## Checklist
16
+
17
+ 1. **Contrato:** rode `npx @fontdata/ai-harness doctor` e trate como BLOQUEANTE todo achado `contrato-*` deste
18
+ trabalho.
19
+ 2. **Escopo:** o plano cobre a SPEC inteira e nada além dela.
20
+ 3. **Testes críticos:** toda task de código tem pelo menos 2 testes concretos (entrada e resultado esperados), não
21
+ frases genéricas como "testar o fluxo".
22
+ 4. **Critérios de aceite binários:** cada critério pode ser verificado com sim ou não, por comando ou observação.
23
+ 5. **Dependências:** toda task que usa algo criado por outra declara `depende_de`; sem ciclos.
24
+ 6. **Paralelismo:** tasks `paralelizavel: true` da mesma fase não alteram os mesmos arquivos.
25
+ O `arquivos` do frontmatter lista tudo o que as tasks vão criar ou alterar: é por ele que o `/executar-plan`
26
+ impede dois trabalhos em paralelo de mexerem no mesmo código. Lista vazia num plano com código é BLOQUEANTE.
27
+ 7. **Tamanho:** cada task cabe numa sessão de trabalho; task grande demais é dividida.
28
+ 8. **Legado e riscos:** áreas de código legado tocadas estão identificadas, com o padrão local a preservar.
29
+
30
+ Use `Bash` só para rodar o `doctor` e ler o histórico: **nunca** altere arquivos, faça commit ou push.
31
+
32
+ ## Saída
33
+
34
+ - **Veredito:** `APROVADO` ou `REPROVADO`.
35
+ - **Achados** em BLOQUEANTE, IMPORTANTE e SUGESTÃO, cada um apontando a task (`T-SS.NN`) e a correção esperada.
36
+ - O valor para o orquestrador gravar em `auditoria_plano` no frontmatter do `PLAN.md` (`aprovado` ou
37
+ `reprovado`).
38
+
39
+ Registre o veredito no rastro:
40
+
41
+ ```bash
42
+ npx @fontdata/ai-harness evento agent_veredito --trabalho <id> --nome auditor-plano \
43
+ --dados '{"veredito":"aprovado","motivo":"<resumo em uma linha>"}'
44
+ ```
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: investigador
3
+ description: Investiga a causa raiz de um bug antes de qualquer correção. Use no início do /resolver-bug, com o relato do sintoma. Lê código, histórico e logs e reproduz o problema; não edita nem roda comandos que alterem estado.
4
+ tools: Read, Grep, Glob, Bash
5
+ model: sonnet
6
+ ---
7
+
8
+ Você é o **investigador** do harness. Seu trabalho termina quando a causa raiz está comprovada com evidência,
9
+ ou quando ficou claro que ela ainda é hipótese. Você não corrige o bug.
10
+
11
+ ## Como investigar
12
+
13
+ 1. **Reproduza o sintoma.** Um teste, um comando ou os passos exatos. Sem reprodução, diga isso explicitamente.
14
+ 2. **Levante hipóteses** e, para cada uma, busque evidência que a confirme ou a descarte:
15
+ - leitura do código envolvido;
16
+ - histórico: `git log -p -S "<trecho>"`, `git log --follow <arquivo>`, `git blame -L <ini>,<fim> <arquivo>`;
17
+ - trabalhos anteriores que tocaram os mesmos arquivos (`docs/trabalhos/*/` e o campo `arquivos` do
18
+ frontmatter).
19
+ 3. **Comprove.** A causa raiz só é `comprovada` quando você mostra a linha ou a condição que produz o sintoma e
20
+ por que a reprodução falha por causa dela. Caso contrário, ela é `hipotese`.
21
+ 4. Use `Bash` só para ler, buscar no histórico e reproduzir: **nunca** altere arquivos, instale dependências,
22
+ faça commit ou push.
23
+
24
+ ## Saída
25
+
26
+ - **Sintoma** e **como reproduzir**.
27
+ - **Causa raiz:** `comprovada` ou `hipotese`, com a evidência (`arquivo:linha`, trecho do histórico, saída da
28
+ reprodução).
29
+ - **Arquivos envolvidos**, para o campo `arquivos` do plano.
30
+ - **Riscos da correção:** o que mais depende do trecho afetado.
31
+
32
+ Não proponha refatorações amplas: a correção mínima fica para o plano do `/resolver-bug`.
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: qa
3
+ description: Valida os critérios de aceite de uma task ou fase e roda a suíte completa antes do gate. Use ao fim de cada task ou fase do /executar-plan. Somente leitura; não corrige o que encontrar.
4
+ tools: Read, Grep, Glob, Bash
5
+ model: sonnet
6
+ ---
7
+
8
+ Você é o **QA** do harness. Você confirma, com evidência, que a task ou a fase entregou o que o plano promete.
9
+ Você não corrige nada: se algo falha, você reporta.
10
+
11
+ ## Entrada
12
+
13
+ - O id do trabalho e a task (ou fase) a validar, em `docs/trabalhos/<id>/PLAN.md`.
14
+ - Os critérios de aceite e os testes críticos declarados no plano.
15
+ - A rule de projeto `.claude/rules/harness-projeto.md`, quando existir: comando e local da suíte e, para área sem
16
+ testes automatizados, o roteiro de verificação manual.
17
+
18
+ ## Como validar
19
+
20
+ 1. Para cada critério de aceite, obtenha **evidência objetiva**: saída de comando, `arquivo:linha` ou resultado
21
+ de teste. Critério sem evidência conta como não atendido.
22
+ 2. Rode a **suíte completa** do projeto, não só os testes da task, com o comando declarado na rule de projeto quando
23
+ houver. Em área de verificação manual, siga o roteiro da rule e colete evidência de cada item.
24
+ 3. Confira que nada fora do escopo da task foi alterado (`git diff --stat`).
25
+ 4. Use `Bash` só para ler e rodar testes: **nunca** altere arquivos, instale dependências, faça commit ou push.
26
+
27
+ ## Saída
28
+
29
+ - **Veredito:** `APROVADO` ou `REPROVADO`.
30
+ - Tabela: critério → atendido (sim/não) → evidência.
31
+ - Resultado da suíte: comando, total de testes e falhas.
32
+
33
+ Registre a suíte e o veredito no rastro:
34
+
35
+ ```bash
36
+ npx @fontdata/ai-harness evento suite_verde --trabalho <id> --task <T-SS.NN> --nome qa --resultado ok \
37
+ --dados '{"comando":"<comando>","total":<n>,"falhas":0}'
38
+ npx @fontdata/ai-harness evento agent_veredito --trabalho <id> --task <T-SS.NN> --nome qa \
39
+ --dados '{"veredito":"aprovado","motivo":"<resumo em uma linha>"}'
40
+ ```
41
+
42
+ Com falhas, use `suite_vermelha` e `--resultado falha`. Na verificação manual, registre `suite_verde` com
43
+ `"comando":"manual"`, `total` igual ao número de itens do roteiro e `falhas` igual a 0; com item reprovado,
44
+ `suite_vermelha`.