@fontdata/ai-harness 2.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.
Files changed (52) hide show
  1. package/.claude-plugin/marketplace.json +17 -0
  2. package/.claude-plugin/plugin.json +8 -0
  3. package/README.md +239 -0
  4. package/bin/cli.js +122 -0
  5. package/package.json +44 -0
  6. package/src/catalog.js +26 -0
  7. package/src/init.js +317 -0
  8. package/src/installer.js +92 -0
  9. package/src/run.js +99 -0
  10. package/templates/CLAUDE.md +49 -0
  11. package/templates/catalog.json +124 -0
  12. package/templates/commands/atualizar-deps.md +15 -0
  13. package/templates/commands/auditar-performance.md +27 -0
  14. package/templates/commands/auditar-seguranca.md +64 -0
  15. package/templates/commands/auditar-ui.md +38 -0
  16. package/templates/commands/codigo-morto.md +22 -0
  17. package/templates/commands/configurar-auto-tag.md +185 -0
  18. package/templates/commands/configurar-comentarios.md +543 -0
  19. package/templates/commands/configurar-orquestrador.md +13 -0
  20. package/templates/commands/corrigir-review.md +11 -0
  21. package/templates/commands/criar-agents.md +42 -0
  22. package/templates/commands/criar-code-reviewer.md +30 -0
  23. package/templates/commands/criar-hooks.md +32 -0
  24. package/templates/commands/criar-rag.md +25 -0
  25. package/templates/commands/criar-rules.md +31 -0
  26. package/templates/commands/criar-skill.md +22 -0
  27. package/templates/commands/design-system.md +27 -0
  28. package/templates/commands/diagrama.md +17 -0
  29. package/templates/commands/executar-plan.md +36 -0
  30. package/templates/commands/gerar-claude-md.md +66 -0
  31. package/templates/commands/gerar-env.md +16 -0
  32. package/templates/commands/gerar-ignore.md +37 -0
  33. package/templates/commands/gerar-spec.md +33 -0
  34. package/templates/commands/gerar-testes.md +17 -0
  35. package/templates/commands/git-commit-push.md +38 -0
  36. package/templates/commands/harness.md +34 -0
  37. package/templates/commands/nova-tarefa.md +60 -0
  38. package/templates/commands/onboarding.md +21 -0
  39. package/templates/commands/refatorar.md +17 -0
  40. package/templates/commands/resolver-bug.md +25 -0
  41. package/templates/commands/review-por-task.md +16 -0
  42. package/templates/commands/revisar-pr.md +18 -0
  43. package/templates/commands/scaffold.md +25 -0
  44. package/templates/commands/sincronizar-claude-md.md +15 -0
  45. package/templates/commands/validar-commands.md +24 -0
  46. package/templates/commands/validar-ideia.md +36 -0
  47. package/templates/commands/versao.md +159 -0
  48. package/templates/skills/lgpd-checker/ORIGEM.md +21 -0
  49. package/templates/skills/lgpd-checker/SKILL.md +152 -0
  50. package/templates/skills/lgpd-checker/references/checklist.md +155 -0
  51. package/templates/skills/lgpd-checker/references/lgpd-artigos.md +178 -0
  52. package/templates/skills/lgpd-checker/references/report-template.md +117 -0
package/src/init.js ADDED
@@ -0,0 +1,317 @@
1
+ import { existsSync, readdirSync, statSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import * as p from '@clack/prompts';
4
+ import { loadCatalog, allCommands, recommendedCommands } from './catalog.js';
5
+ import { installCommand, installClaudeMd, installMaster, installSkill } from './installer.js';
6
+
7
+ /** Encerra o fluxo com mensagem amigável quando o usuário cancela (Ctrl+C). */
8
+ function bail(value) {
9
+ if (p.isCancel(value)) {
10
+ p.cancel('Operação cancelada.');
11
+ process.exit(0);
12
+ }
13
+ return value;
14
+ }
15
+
16
+ /**
17
+ * Quantos itens a lista pode mostrar de uma vez sem estourar a altura do terminal.
18
+ * Calcula a partir das linhas reais do terminal (process.stdout.rows), deixando
19
+ * margem para a mensagem e os indicadores de rolagem do clack. Sem isso, em um
20
+ * terminal baixo a lista sobrepõe as renderizações ("itens duplicados").
21
+ */
22
+ function viewportItems(min = 3, reserve = 6) {
23
+ const rows = process.stdout.rows || 20;
24
+ return Math.max(min, rows - reserve);
25
+ }
26
+
27
+ /**
28
+ * Manifestos de stack: se algum existir na raiz, é um projeto já existente.
29
+ */
30
+ const PROJECT_MANIFESTS = [
31
+ 'package.json', 'pyproject.toml', 'requirements.txt', 'go.mod',
32
+ 'Cargo.toml', 'pom.xml', 'build.gradle', 'build.gradle.kts',
33
+ 'composer.json', 'Gemfile', 'Makefile', 'CMakeLists.txt',
34
+ 'tsconfig.json', 'pubspec.yaml', 'mix.exs',
35
+ ];
36
+
37
+ /** Pastas de código-fonte que, se tiverem conteúdo, indicam projeto existente. */
38
+ const SOURCE_DIRS = ['src', 'app', 'lib', 'source'];
39
+
40
+ /**
41
+ * Detecta se a pasta alvo já é um projeto com código ('existing') ou está
42
+ * essencialmente vazia/recém-criada ('new'). Ignora .git e .claude — a presença
43
+ * deles sozinha não caracteriza um projeto existente.
44
+ */
45
+ function detectProjectKind(repoPath) {
46
+ // 1. Manifesto de stack na raiz.
47
+ if (PROJECT_MANIFESTS.some((f) => existsSync(resolve(repoPath, f)))) return 'existing';
48
+
49
+ // 2. Arquivo de solução/projeto .NET na raiz.
50
+ let rootEntries = [];
51
+ try {
52
+ rootEntries = readdirSync(repoPath);
53
+ } catch {
54
+ return 'new';
55
+ }
56
+ if (rootEntries.some((f) => /\.(sln|csproj|fsproj|vbproj)$/i.test(f))) return 'existing';
57
+
58
+ // 3. Pasta de código-fonte com algum conteúdo.
59
+ for (const dir of SOURCE_DIRS) {
60
+ const dirPath = resolve(repoPath, dir);
61
+ try {
62
+ if (statSync(dirPath).isDirectory() && readdirSync(dirPath).length) return 'existing';
63
+ } catch {
64
+ // pasta não existe — segue.
65
+ }
66
+ }
67
+
68
+ return 'new';
69
+ }
70
+
71
+ /**
72
+ * Seleção interativa em dois níveis: menu de grupos → submenu de itens do grupo
73
+ * → volta ao menu de grupos. Mantém cada lista pequena (cabe em terminais baixos)
74
+ * e mostra o quanto já foi marcado em cada grupo. Retorna os comandos escolhidos.
75
+ *
76
+ * `initialIds` define os comandos já marcados ao abrir (ex.: recomendados em
77
+ * projeto novo). Em projeto existente vem vazio — o usuário marca só os avulsos.
78
+ */
79
+ async function selectByGroups(catalog, initialIds = new Set()) {
80
+ const selectedIds = new Set(initialIds);
81
+ const DONE = '__done__';
82
+
83
+ for (;;) {
84
+ const groupOptions = catalog.groups.map((g) => {
85
+ const chosen = g.commands.filter((c) => selectedIds.has(c.id)).length;
86
+ return {
87
+ value: g.id,
88
+ label: `${g.title} (${chosen}/${g.commands.length})`,
89
+ hint: chosen ? `${chosen} marcado(s)` : undefined,
90
+ };
91
+ });
92
+ groupOptions.push({
93
+ value: DONE,
94
+ label: `✓ Concluir e instalar (${selectedIds.size} comando(s) marcado(s))`,
95
+ });
96
+
97
+ const groupChoice = bail(
98
+ await p.select({
99
+ message: 'Escolha um grupo para marcar comandos — ou Concluir:',
100
+ options: groupOptions,
101
+ maxItems: viewportItems(),
102
+ })
103
+ );
104
+
105
+ if (groupChoice === DONE) break;
106
+
107
+ const group = catalog.groups.find((g) => g.id === groupChoice);
108
+ const picked = bail(
109
+ await p.multiselect({
110
+ message: `${group.title} — espaço marca, enter volta ao menu de grupos:`,
111
+ options: group.commands.map((c) => ({ value: c.id, label: c.title, hint: c.description })),
112
+ initialValues: group.commands.filter((c) => selectedIds.has(c.id)).map((c) => c.id),
113
+ required: false,
114
+ maxItems: viewportItems(),
115
+ })
116
+ );
117
+
118
+ // Reconcilia só este grupo: limpa os ids dele e re-adiciona os que ficaram marcados.
119
+ for (const c of group.commands) selectedIds.delete(c.id);
120
+ for (const id of picked) selectedIds.add(id);
121
+ }
122
+
123
+ return allCommands(catalog).filter((c) => selectedIds.has(c.id));
124
+ }
125
+
126
+ export async function runInit(initialPath) {
127
+ const catalog = loadCatalog();
128
+
129
+ p.intro('Fontdata · ai-harness — instalador do harness para Claude Code');
130
+
131
+ // 1. Diretório alvo
132
+ const repoInput = bail(
133
+ await p.text({
134
+ message: 'Em qual pasta do projeto deseja instalar?',
135
+ placeholder: '. (pasta atual)',
136
+ initialValue: initialPath || '.',
137
+ })
138
+ );
139
+ const repoPath = resolve(process.cwd(), repoInput.trim() || '.');
140
+
141
+ if (!existsSync(repoPath)) {
142
+ p.cancel(`Pasta não encontrada: ${repoPath}`);
143
+ process.exit(1);
144
+ }
145
+
146
+ // Detecta o tipo de projeto: muda como a seleção "Manual" começa.
147
+ const projectKind = detectProjectKind(repoPath);
148
+ p.note(
149
+ projectKind === 'existing'
150
+ ? 'Projeto existente detectado. Na opção "Escolher manualmente" nada vem marcado — ideal para cadastrar comandos avulsos.'
151
+ : 'Projeto novo detectado. Na opção "Escolher manualmente" os comandos recomendados já vêm marcados.',
152
+ projectKind === 'existing' ? 'Projeto existente' : 'Projeto novo'
153
+ );
154
+
155
+ // 2. O que instalar
156
+ const scope = bail(
157
+ await p.select({
158
+ message: 'O que deseja instalar?',
159
+ options: [
160
+ { value: 'recommended', label: 'Conjunto recomendado', hint: 'núcleo de setup + dia a dia' },
161
+ { value: 'all', label: 'Tudo', hint: `${allCommands(catalog).length} comandos` },
162
+ {
163
+ value: 'manual',
164
+ label: 'Escolher manualmente',
165
+ hint: projectKind === 'existing' ? 'seleção por categoria (nada marcado)' : 'seleção por categoria (recomendados marcados)',
166
+ },
167
+ ],
168
+ })
169
+ );
170
+
171
+ let selected;
172
+ if (scope === 'all') {
173
+ selected = allCommands(catalog);
174
+ } else if (scope === 'recommended') {
175
+ selected = recommendedCommands(catalog);
176
+ } else {
177
+ // Projeto novo começa com os recomendados marcados; existente começa vazio.
178
+ const initialIds =
179
+ projectKind === 'new'
180
+ ? new Set(recommendedCommands(catalog).map((c) => c.id))
181
+ : new Set();
182
+ selected = await selectByGroups(catalog, initialIds);
183
+ }
184
+
185
+ // 3. Comando-mestre /harness
186
+ const wantMaster = bail(
187
+ await p.confirm({
188
+ message: 'Incluir também o comando-mestre /harness (conduz a sequência de setup em ordem)?',
189
+ initialValue: true,
190
+ })
191
+ );
192
+
193
+ // Se o /harness foi escolhido, garante que os comandos que ele invoca também sejam instalados.
194
+ let autoAdded = [];
195
+ if (wantMaster) {
196
+ const required = catalog.master.requires || [];
197
+ const selectedIds = new Set(selected.map((c) => c.id));
198
+ const missing = allCommands(catalog).filter(
199
+ (c) => required.includes(c.id) && !selectedIds.has(c.id)
200
+ );
201
+ if (missing.length) {
202
+ selected = [...selected, ...missing];
203
+ autoAdded = missing.map((c) => c.title);
204
+ }
205
+ }
206
+
207
+ // Resolve dependências por comando (campo `requires`): se um comando selecionado
208
+ // depende de outro, o dependente entra junto, mesmo que não tenha sido marcado —
209
+ // como acontece com o /harness.
210
+ {
211
+ const selectedIds = new Set(selected.map((c) => c.id));
212
+ const deps = allCommands(catalog).filter(
213
+ (c) =>
214
+ !selectedIds.has(c.id) &&
215
+ selected.some((sel) => (sel.requires || []).includes(c.id))
216
+ );
217
+ if (deps.length) {
218
+ selected = [...selected, ...deps];
219
+ autoAdded = [...autoAdded, ...deps.map((c) => c.title)];
220
+ }
221
+ }
222
+
223
+ // 4. CLAUDE.md base
224
+ const claudeExists = existsSync(resolve(repoPath, catalog.claudeMd.target));
225
+ let claudeMode = 'create';
226
+ const wantClaude = bail(
227
+ await p.confirm({
228
+ message: claudeExists
229
+ ? 'Já existe um CLAUDE.md na raiz. Deseja tratá-lo?'
230
+ : 'Criar o CLAUDE.md base na raiz (filosofia de orquestração/TDD)?',
231
+ initialValue: !claudeExists,
232
+ })
233
+ );
234
+ if (wantClaude && claudeExists) {
235
+ claudeMode = bail(
236
+ await p.select({
237
+ message: 'O que fazer com o CLAUDE.md existente?',
238
+ options: [
239
+ { value: 'backup', label: 'Sobrescrever criando .bak', hint: 'recomendado' },
240
+ { value: 'overwrite', label: 'Sobrescrever sem backup' },
241
+ { value: 'skip', label: 'Manter o existente' },
242
+ ],
243
+ })
244
+ );
245
+ }
246
+
247
+ // 5. Confirmação final
248
+ if (selected.length === 0 && !wantMaster && !wantClaude) {
249
+ p.outro('Nada selecionado. Nenhuma alteração feita.');
250
+ return;
251
+ }
252
+
253
+ // Skills exigidas pelos comandos selecionados (campo `requiresSkills`).
254
+ const skillCatalog = catalog.skills || [];
255
+ const neededSkillIds = new Set(selected.flatMap((c) => c.requiresSkills || []));
256
+ const neededSkills = skillCatalog.filter((sk) => neededSkillIds.has(sk.id));
257
+
258
+ // Política global de conflito: perguntamos UMA vez se houver risco de sobrescrever.
259
+ const anyConflict =
260
+ selected.some((c) => existsSync(resolve(repoPath, '.claude', 'commands', `${c.id}.md`))) ||
261
+ (wantMaster && existsSync(resolve(repoPath, '.claude', 'commands', `${catalog.master.id}.md`))) ||
262
+ neededSkills.some((sk) => existsSync(resolve(repoPath, '.claude', 'skills', sk.id)));
263
+
264
+ let conflictPolicy = 'overwrite';
265
+ if (anyConflict) {
266
+ conflictPolicy = bail(
267
+ await p.select({
268
+ message: 'Alguns itens já existem no destino. O que fazer com os conflitos?',
269
+ options: [
270
+ { value: 'overwrite', label: 'Sobrescrever os existentes' },
271
+ { value: 'skip', label: 'Pular os que já existem' },
272
+ ],
273
+ })
274
+ );
275
+ }
276
+
277
+ // Execução
278
+ const s = p.spinner();
279
+ s.start('Instalando...');
280
+
281
+ const results = { created: [], overwritten: [], skipped: [] };
282
+ for (const cmd of selected) {
283
+ const status = installCommand(cmd, repoPath, () => conflictPolicy);
284
+ results[status].push(cmd.title);
285
+ }
286
+ if (wantMaster) {
287
+ const status = installMaster(catalog, repoPath, () => conflictPolicy);
288
+ results[status].push(catalog.master.title);
289
+ }
290
+
291
+ const skillResults = { created: [], overwritten: [], skipped: [] };
292
+ for (const skill of neededSkills) {
293
+ const status = installSkill(skill, repoPath, () => conflictPolicy);
294
+ skillResults[status].push(skill.title);
295
+ }
296
+
297
+ let claudeResult = null;
298
+ if (wantClaude) {
299
+ claudeResult = installClaudeMd(catalog, repoPath, claudeExists ? claudeMode : 'create');
300
+ }
301
+
302
+ s.stop('Instalação concluída.');
303
+
304
+ // Resumo
305
+ const lines = [];
306
+ if (results.created.length) lines.push(`Criados (${results.created.length}): ${results.created.join(', ')}`);
307
+ if (results.overwritten.length) lines.push(`Sobrescritos (${results.overwritten.length}): ${results.overwritten.join(', ')}`);
308
+ if (results.skipped.length) lines.push(`Pulados (${results.skipped.length}): ${results.skipped.join(', ')}`);
309
+ const skillsInstalled = [...skillResults.created, ...skillResults.overwritten];
310
+ if (skillsInstalled.length) lines.push(`Skills (${skillsInstalled.length}): ${skillsInstalled.join(', ')}`);
311
+ if (skillResults.skipped.length) lines.push(`Skills puladas (${skillResults.skipped.length}): ${skillResults.skipped.join(', ')}`);
312
+ if (autoAdded.length) lines.push(`Adicionados como dependência (${autoAdded.length}): ${autoAdded.join(', ')}`);
313
+ if (claudeResult) lines.push(`CLAUDE.md: ${claudeResult}`);
314
+ p.note(lines.join('\n') || 'Nada a instalar.', `Resumo · ${repoPath}`);
315
+
316
+ p.outro('Abra o projeto no Claude Code e digite "/" para ver os comandos instalados.');
317
+ }
@@ -0,0 +1,92 @@
1
+ import { cpSync, copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { templatesDir } from './catalog.js';
4
+
5
+ /** Garante que o diretório de um arquivo exista. */
6
+ function ensureDir(filePath) {
7
+ mkdirSync(dirname(filePath), { recursive: true });
8
+ }
9
+
10
+ /**
11
+ * Copia um comando (template) para <repo>/.claude/commands/<id>.md.
12
+ * Retorna o status da operação: 'created' | 'overwritten' | 'skipped'.
13
+ *
14
+ * @param {object} cmd Item do catálogo (precisa de id e file).
15
+ * @param {string} repoPath Raiz do repositório alvo.
16
+ * @param {function} resolveConflict (destPath) => 'overwrite' | 'skip'. Chamado só se o destino existir.
17
+ */
18
+ export function installCommand(cmd, repoPath, resolveConflict) {
19
+ const source = join(templatesDir, cmd.file);
20
+ const dest = join(repoPath, '.claude', 'commands', `${cmd.id}.md`);
21
+
22
+ if (existsSync(dest)) {
23
+ const decision = resolveConflict ? resolveConflict(dest) : 'overwrite';
24
+ if (decision === 'skip') return 'skipped';
25
+ ensureDir(dest);
26
+ copyFileSync(source, dest);
27
+ return 'overwritten';
28
+ }
29
+
30
+ ensureDir(dest);
31
+ copyFileSync(source, dest);
32
+ return 'created';
33
+ }
34
+
35
+ /**
36
+ * Instala/atualiza o CLAUDE.md base na raiz do repositório.
37
+ * mode: 'overwrite' | 'skip' | 'backup' (cria CLAUDE.md.bak antes de sobrescrever).
38
+ * Retorna 'created' | 'overwritten' | 'skipped' | 'backed-up'.
39
+ */
40
+ export function installClaudeMd(catalog, repoPath, mode = 'overwrite') {
41
+ const source = join(templatesDir, catalog.claudeMd.file);
42
+ const dest = join(repoPath, catalog.claudeMd.target);
43
+
44
+ if (!existsSync(dest)) {
45
+ writeFileSync(dest, readFileSync(source, 'utf8'));
46
+ return 'created';
47
+ }
48
+
49
+ if (mode === 'skip') return 'skipped';
50
+
51
+ if (mode === 'backup') {
52
+ copyFileSync(dest, `${dest}.bak`);
53
+ writeFileSync(dest, readFileSync(source, 'utf8'));
54
+ return 'backed-up';
55
+ }
56
+
57
+ writeFileSync(dest, readFileSync(source, 'utf8'));
58
+ return 'overwritten';
59
+ }
60
+
61
+ /**
62
+ * Copia uma skill (pasta com SKILL.md e references/) para
63
+ * <repo>/.claude/skills/<id>/. Retorna 'created' | 'overwritten' | 'skipped'.
64
+ *
65
+ * @param {object} skill Item do catálogo (precisa de id e dir).
66
+ * @param {string} repoPath Raiz do repositório alvo.
67
+ * @param {function} resolveConflict (destPath) => 'overwrite' | 'skip'. Chamado só se o destino existir.
68
+ */
69
+ export function installSkill(skill, repoPath, resolveConflict) {
70
+ const source = join(templatesDir, skill.dir);
71
+ const dest = join(repoPath, '.claude', 'skills', skill.id);
72
+
73
+ if (existsSync(dest)) {
74
+ const decision = resolveConflict ? resolveConflict(dest) : 'overwrite';
75
+ if (decision === 'skip') return 'skipped';
76
+ cpSync(source, dest, { recursive: true });
77
+ return 'overwritten';
78
+ }
79
+
80
+ mkdirSync(dirname(dest), { recursive: true });
81
+ cpSync(source, dest, { recursive: true });
82
+ return 'created';
83
+ }
84
+
85
+ /** Instala o comando-mestre /harness (mesmo mecanismo de installCommand). */
86
+ export function installMaster(catalog, repoPath, resolveConflict) {
87
+ return installCommand(
88
+ { id: catalog.master.id, file: catalog.master.file },
89
+ repoPath,
90
+ resolveConflict
91
+ );
92
+ }
package/src/run.js ADDED
@@ -0,0 +1,99 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { readFileSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { dirname, join } from 'node:path';
5
+ import { loadCatalog, allCommands } from './catalog.js';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+
9
+ /** Raiz do pacote — é ela que vira o plugin carregado via `claude --plugin-dir`. */
10
+ export const pluginRoot = join(__dirname, '..');
11
+
12
+ /** Nome do plugin, lido do manifesto para não duplicar a string. */
13
+ export function pluginName() {
14
+ const manifest = JSON.parse(
15
+ readFileSync(join(pluginRoot, '.claude-plugin', 'plugin.json'), 'utf8')
16
+ );
17
+ return manifest.name;
18
+ }
19
+
20
+ /**
21
+ * Ids executáveis: os comandos dos grupos mais o comando-mestre, que fica fora
22
+ * de `groups` no catálogo.
23
+ */
24
+ export function runnableIds(catalog = loadCatalog()) {
25
+ const ids = allCommands(catalog).map((cmd) => cmd.id);
26
+ if (catalog.master?.id) ids.unshift(catalog.master.id);
27
+ return [...new Set(ids)];
28
+ }
29
+
30
+ /** Ids mais parecidos com o que foi digitado, para sugerir em caso de erro. */
31
+ export function suggestIds(input, ids) {
32
+ const needle = input.toLowerCase();
33
+ return ids
34
+ .filter((id) => id.includes(needle) || needle.includes(id) || id.startsWith(needle.slice(0, 4)))
35
+ .slice(0, 5);
36
+ }
37
+
38
+ const SYSTEM_NOTE =
39
+ 'Os slash commands do harness Fontdata estão disponíveis nesta sessão com o prefixo ' +
40
+ '`/PLUGIN:` (ex.: `/PLUGIN:gerar-spec`). Quando uma instrução referenciar um comando ' +
41
+ 'como `/nome`, entenda que se trata de `/PLUGIN:nome`.';
42
+
43
+ /**
44
+ * Executa um comando do catálogo abrindo o Claude Code na pasta atual, com o
45
+ * pacote carregado como plugin da sessão — sem instalar nada no projeto.
46
+ */
47
+ export function runCommand(id, args = [], claudeFlags = []) {
48
+ const catalog = loadCatalog();
49
+ const ids = runnableIds(catalog);
50
+ const wanted = id.replace(/^\//, '');
51
+
52
+ if (!ids.includes(wanted)) {
53
+ const near = suggestIds(wanted, ids);
54
+ console.error(`Comando desconhecido: ${wanted}`);
55
+ if (near.length) console.error(`\nVocê quis dizer: ${near.join(', ')}`);
56
+ console.error('\nRode `npx @fontdata/ai-harness list` para ver o catálogo completo.');
57
+ return 1;
58
+ }
59
+
60
+ const name = pluginName();
61
+ const prompt = [`/${name}:${wanted}`, ...args].join(' ').trim();
62
+ const argv = [
63
+ '--plugin-dir',
64
+ pluginRoot,
65
+ '--append-system-prompt',
66
+ SYSTEM_NOTE.replaceAll('PLUGIN', name),
67
+ ...claudeFlags,
68
+ '--',
69
+ prompt,
70
+ ];
71
+
72
+ const result = spawn(argv);
73
+
74
+ if (result.error?.code === 'ENOENT') {
75
+ console.error(
76
+ 'Claude Code não encontrado no PATH.\n' +
77
+ 'Instale com: npm install -g @anthropic-ai/claude-code'
78
+ );
79
+ return 127;
80
+ }
81
+ if (result.error) {
82
+ console.error('Erro ao executar o Claude Code:', result.error.message);
83
+ return 1;
84
+ }
85
+ return result.status ?? 0;
86
+ }
87
+
88
+ /**
89
+ * No Windows o `claude` costuma ser um `.cmd`, que o spawn sem shell não
90
+ * resolve; nesse caso repetimos a chamada pelo shell, com os argumentos citados.
91
+ */
92
+ function spawn(argv) {
93
+ const direct = spawnSync('claude', argv, { stdio: 'inherit', cwd: process.cwd() });
94
+ if (direct.error?.code === 'ENOENT' && process.platform === 'win32') {
95
+ const quoted = argv.map((arg) => `"${String(arg).replaceAll('"', '\\"')}"`).join(' ');
96
+ return spawnSync(`claude ${quoted}`, { stdio: 'inherit', cwd: process.cwd(), shell: true });
97
+ }
98
+ return direct;
99
+ }
@@ -0,0 +1,49 @@
1
+ # [NOME DO PROJETO]
2
+ > Substitua esta seção rodando `/gerar-claude-md` depois de gerar o `SPEC.md`.
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`).
6
+
7
+ ## Filosofia de trabalho
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.
16
+
17
+ ## Modelo de arquivos
18
+
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`). |
27
+
28
+ ## Fluxo recomendado (projeto novo)
29
+
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
37
+ 8. `/executar-plan` — orquestra a implementação
38
+
39
+ No dia a dia: `/nova-tarefa`, `/resolver-bug`, `/git-commit-push`.
40
+
41
+ ## Nunca fazer
42
+
43
+ - 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.
46
+ - Nunca usar `git push --force` ou comandos destrutivos sem confirmação.
47
+ - 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.