ganbatte-os 0.2.34 → 0.2.35

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.
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * migrate-task-status.js
4
+ *
5
+ * Migra task files do formato bugado (`## Status` no body, sem frontmatter)
6
+ * para o formato canonico (frontmatter YAML com `status:`).
7
+ *
8
+ * Origem do bug: plan-to-tasks/SKILL.md tinha template inline obsoleto sem
9
+ * `status:`. Tasks geradas ficavam travadas em `pendente` mesmo apos
10
+ * *execute-plan rodar codigo, porque progress-tracker procura no frontmatter
11
+ * e nao acha. Corrigido em commit subsequente; este script reabilita planos
12
+ * preexistentes.
13
+ *
14
+ * Uso:
15
+ * node migrate-task-status.js <plan-dir>
16
+ * node migrate-task-status.js <plan-dir> --infer-from-diff
17
+ */
18
+
19
+ const fs = require('node:fs');
20
+ const path = require('node:path');
21
+ const { execFileSync } = require('node:child_process');
22
+
23
+ const args = process.argv.slice(2);
24
+ const planDir = args[0];
25
+ const inferFromDiff = args.includes('--infer-from-diff');
26
+
27
+ if (!planDir || !fs.existsSync(planDir)) {
28
+ console.error('uso: migrate-task-status.js <plan-dir> [--infer-from-diff]');
29
+ process.exit(1);
30
+ }
31
+
32
+ const tasksDir = path.join(planDir, 'tasks');
33
+ if (!fs.existsSync(tasksDir)) {
34
+ console.error(`tasks dir nao encontrado: ${tasksDir}`);
35
+ process.exit(1);
36
+ }
37
+
38
+ const planFile = path.join(planDir, 'plan.md');
39
+ const planSlug = path.basename(planDir);
40
+ const planId = planSlug.split('-').slice(0, 2).join('-');
41
+
42
+ function gitLog(args) {
43
+ try {
44
+ return execFileSync('git', args, { encoding: 'utf8' }).trim();
45
+ } catch {
46
+ return '';
47
+ }
48
+ }
49
+
50
+ const taskFiles = fs.readdirSync(tasksDir)
51
+ .filter((f) => /^T-\d+.*\.md$/.test(f))
52
+ .map((f) => path.join(tasksDir, f));
53
+
54
+ let migrated = 0;
55
+ let skipped = 0;
56
+ let failed = 0;
57
+
58
+ for (const file of taskFiles) {
59
+ const raw = fs.readFileSync(file, 'utf8');
60
+
61
+ if (raw.startsWith('---\n')) {
62
+ skipped++;
63
+ continue;
64
+ }
65
+
66
+ const titleMatch = raw.match(/^# (T-\d+(?:-\d+)?)[ —-]+(.+?)$/m);
67
+ const statusMatch = raw.match(/^## Status\s*\n+`?(\w[\w-]*)`?/m);
68
+
69
+ if (!titleMatch) {
70
+ console.error(`falha: ${file} — sem heading T-NN`);
71
+ failed++;
72
+ continue;
73
+ }
74
+
75
+ const taskId = titleMatch[1];
76
+ const title = titleMatch[2].trim();
77
+ const seqRaw = (taskId.match(/T-(\d+)(?:-(\d+))?/) || [])[2] || taskId.split('-')[1];
78
+ const bodyStatus = statusMatch ? statusMatch[1] : 'pendente';
79
+
80
+ let finalStatus = bodyStatus;
81
+ if (inferFromDiff && bodyStatus === 'pendente') {
82
+ const addCommit = gitLog(['log', '--diff-filter=A', '--format=%H', '--', planFile]);
83
+ const sinceCommit = addCommit ? addCommit.split('\n').pop() : '';
84
+ if (sinceCommit) {
85
+ const diff = gitLog(['log', `${sinceCommit}..HEAD`, '--name-only', '--pretty=format:']);
86
+ if (diff) finalStatus = 'validacao';
87
+ }
88
+ }
89
+
90
+ const bodyWithoutStatus = raw
91
+ .replace(/^## Status\s*\n+`?\w[\w-]*`?\s*\n+/m, '')
92
+ .replace(/^# T-\d+.*?\n+/m, '');
93
+
94
+ const frontmatter = `---
95
+ id: ${taskId}
96
+ plan_id: ${planId}
97
+ seq: ${parseInt(seqRaw, 10)}
98
+ title: ${title}
99
+ area: frontend
100
+ labels: [agent:dev]
101
+ priority: P1
102
+ estimate: "4h"
103
+ status: ${finalStatus}
104
+ valida_em: ""
105
+ depends_on_backend: []
106
+ interaction_target: []
107
+ override_target: []
108
+ assignees: []
109
+ links: []
110
+ ---
111
+
112
+ # ${taskId} — ${title}
113
+
114
+ `;
115
+
116
+ fs.writeFileSync(file, frontmatter + bodyWithoutStatus);
117
+ migrated++;
118
+ console.log(`migrado: ${path.basename(file)} → status=${finalStatus}`);
119
+ }
120
+
121
+ console.log(`\n[migrate-task-status] ${migrated} migrados | ${skipped} ja-validos | ${failed} falhas`);
122
+ process.exit(failed > 0 ? 1 : 0);
@@ -22,15 +22,53 @@ function relativeTarget(fromFile, targetFile) {
22
22
  return path.relative(path.dirname(fromFile), targetFile).replace(/\\/g, '/');
23
23
  }
24
24
 
25
+ function agentSlug(id) {
26
+ return id.startsWith('gos-') ? id : `gos-${id}`;
27
+ }
28
+
29
+ function cleanupStaleAdapters() {
30
+ // Remove arquivos .md soltos no root de skills/ (formato legado) em todas as IDEs.
31
+ // Codex/Qwen/Gemini/Opencode/Antigravity esperam <slug>/SKILL.md (diretorio), nao .md solto.
32
+ // .agent e o diretorio canonico do Antigravity (workspace scope: .agent/skills/, .agent/workflows/).
33
+ const allIdes = ['.codex', '.qwen', '.gemini', '.opencode', '.agent'];
34
+ for (const ide of allIdes) {
35
+ const skillsDir = path.join(root, ide, 'skills');
36
+ if (fs.existsSync(skillsDir)) {
37
+ for (const entry of fs.readdirSync(skillsDir)) {
38
+ const full = path.join(skillsDir, entry);
39
+ if (fs.statSync(full).isFile() && entry.endsWith('.md')) {
40
+ fs.unlinkSync(full);
41
+ }
42
+ }
43
+ }
44
+ const agentsDir = path.join(root, ide, 'agents');
45
+ if (fs.existsSync(agentsDir)) {
46
+ for (const entry of fs.readdirSync(agentsDir)) {
47
+ if (/^gos-gos-/.test(entry)) {
48
+ fs.unlinkSync(path.join(agentsDir, entry));
49
+ }
50
+ }
51
+ }
52
+ }
53
+
54
+ // Legado: .antigravity/ era o diretorio errado (Antigravity nunca leu isso — usa .agent/).
55
+ // Remove silenciosamente para evitar drift entre IDEs.
56
+ const legacyAntigravity = path.join(root, '.antigravity');
57
+ if (fs.existsSync(legacyAntigravity)) {
58
+ fs.rmSync(legacyAntigravity, { recursive: true, force: true });
59
+ }
60
+ }
61
+
25
62
  function agentWrapper(agentId, target) {
26
63
  return `# ${agentId}\n\nFonte canonica: \`${target}\`\n\nLeia e siga o perfil em \`${target}\`.\nEste arquivo existe apenas como adapter fino para a IDE.`;
27
64
  }
28
65
 
29
66
  function skillWrapper(slug, target, description) {
30
- const body = `# gos-${slug}\n\nFonte canonica: \`${target}\`\n\nLeia e siga a skill em \`${target}\`.\nEste arquivo existe apenas como adapter fino para a IDE.`;
67
+ const fullSlug = slug.startsWith('gos-') ? slug : `gos-${slug}`;
68
+ const body = `# ${fullSlug}\n\nFonte canonica: \`${target}\`\n\nLeia e siga a skill em \`${target}\`.\nEste arquivo existe apenas como adapter fino para a IDE.`;
31
69
  if (!description) return body;
32
70
  const desc = String(description).replace(/"/g, '\\"').replace(/\n/g, ' ').slice(0, 200);
33
- return `---\nname: "gos-${slug}"\ndescription: "${desc}"\n---\n\n${body}`;
71
+ return `---\nname: "${fullSlug}"\ndescription: "${desc}"\n---\n\n${body}`;
34
72
  }
35
73
 
36
74
  function qwenCommandWrapper(name, description, target) {
@@ -38,10 +76,11 @@ function qwenCommandWrapper(name, description, target) {
38
76
  return `---\ndescription: "${desc}"\n---\n\n# ${name} (Qwen Command Adapter)\n\n> Adapter para Qwen Code. Fonte canonica: \`${target}\`.\n\nCANONICAL-SOURCE: ${target}\n\n## Adapter Contract\n\n1. Leia o arquivo canonico em **CANONICAL-SOURCE** por completo.\n2. Execute as instrucoes desse arquivo como fonte primaria.\n3. Argumentos do usuario: {{args}}`;
39
77
  }
40
78
 
41
- function claudeCommandWrapper(name, description, target, argumentHint) {
79
+ function claudeCommandWrapper(name, description, target, argumentHint, ideLabel) {
42
80
  const desc = (description || name).replace(/"/g, '\\"').replace(/\n/g, ' ').slice(0, 200);
43
81
  const hint = argumentHint ? `\nargument-hint: "${argumentHint.replace(/"/g, '\\"')}"` : '\nargument-hint: "[argumentos opcionais]"';
44
- return `---\ndescription: "${desc}"${hint}\n---\n\n# ${name} (Claude Adapter)\n\n> Adapter fino para Claude. Fonte canonica: \`${target}\`.\n\nCANONICAL-SOURCE: ${target}\n\n## Adapter Contract\n\n1. Leia o arquivo canonico indicado em **CANONICAL-SOURCE** por completo.\n2. Execute as instrucoes desse arquivo como fonte primaria.\n3. Argumentos do usuario: $ARGUMENTS`;
82
+ const label = ideLabel || 'Claude';
83
+ return `---\ndescription: "${desc}"${hint}\n---\n\n# ${name} (${label} Adapter)\n\n> Adapter fino para ${label}. Fonte canonica: \`${target}\`.\n\nCANONICAL-SOURCE: ${target}\n\n## Adapter Contract\n\n1. Leia o arquivo canonico indicado em **CANONICAL-SOURCE** por completo.\n2. Execute as instrucoes desse arquivo como fonte primaria.\n3. Argumentos do usuario: $ARGUMENTS`;
45
84
  }
46
85
 
47
86
  function extractAgentDescription(filePath) {
@@ -86,32 +125,51 @@ function main() {
86
125
  const agents = readJson(path.join(root, '.gos', 'agents', 'profiles', 'index.json')).profiles;
87
126
  const skills = readJson(path.join(root, '.gos', 'skills', 'registry.json')).skills;
88
127
 
128
+ cleanupStaleAdapters();
129
+
89
130
  for (const agent of agents) {
90
131
  const agentProfilePath = path.join(root, '.gos', 'agents', 'profiles', agent.path);
91
132
  const agentDesc = extractAgentDescription(agentProfilePath) || `${agent.id} agent`;
133
+ const aSlug = agentSlug(agent.id);
92
134
 
93
135
  // Claude commands (requires YAML frontmatter with description)
94
136
  const claudeFile = path.join(root, '.claude', 'commands', 'gos', 'agents', `${agent.id}.md`);
95
- writeFile(claudeFile, claudeCommandWrapper(`gos-${agent.id}`, agentDesc, relativeTarget(claudeFile, agentProfilePath)));
137
+ writeFile(claudeFile, claudeCommandWrapper(aSlug, agentDesc, relativeTarget(claudeFile, agentProfilePath), '', 'Claude'));
96
138
 
97
139
  // Qwen commands (requires YAML frontmatter with description)
98
140
  const qwenCmd = path.join(root, '.qwen', 'commands', 'gos', 'agents', `${agent.id}.md`);
99
- writeFile(qwenCmd, qwenCommandWrapper(`gos-${agent.id}`, agentDesc, relativeTarget(qwenCmd, agentProfilePath)));
141
+ writeFile(qwenCmd, qwenCommandWrapper(aSlug, agentDesc, relativeTarget(qwenCmd, agentProfilePath)));
100
142
 
101
143
  // Qwen sub-agents
102
- const qwenAgent = path.join(root, '.qwen', 'agents', `gos-${agent.id}.md`);
144
+ const qwenAgent = path.join(root, '.qwen', 'agents', `${aSlug}.md`);
103
145
  const agentTarget = relativeTarget(qwenAgent, agentProfilePath);
104
146
  const safeDesc = agentDesc.replace(/"/g, '\\"').replace(/\n/g, ' ').slice(0, 200);
105
- writeFile(qwenAgent, `---\nname: "gos-${agent.id}"\ndescription: "${safeDesc}"\nmodel: inherit\ntools:\n - Read\n - Glob\n - Grep\n - Bash\n - Edit\n - Write\n---\n\nFonte canonica: \`${agentTarget}\`\nLeia e siga o perfil em \`${agentTarget}\`.`);
147
+ writeFile(qwenAgent, `---\nname: "${aSlug}"\ndescription: "${safeDesc}"\nmodel: inherit\ntools:\n - Read\n - Glob\n - Grep\n - Bash\n - Edit\n - Write\n---\n\nFonte canonica: \`${agentTarget}\`\nLeia e siga o perfil em \`${agentTarget}\`.`);
106
148
 
107
149
  // Codex commands (slash commands no Codex IDE Extension)
108
150
  const codexCmd = path.join(root, '.codex', 'commands', 'gos', 'agents', `${agent.id}.md`);
109
- writeFile(codexCmd, claudeCommandWrapper(`gos-${agent.id}`, agentDesc, relativeTarget(codexCmd, agentProfilePath)));
151
+ writeFile(codexCmd, claudeCommandWrapper(aSlug, agentDesc, relativeTarget(codexCmd, agentProfilePath), '', 'Codex'));
110
152
 
111
153
  // Codex sub-agents (Codex IDE espera .codex/agents/<id>.md)
112
- const codexAgent = path.join(root, '.codex', 'agents', `gos-${agent.id}.md`);
154
+ const codexAgent = path.join(root, '.codex', 'agents', `${aSlug}.md`);
113
155
  const codexAgentTarget = relativeTarget(codexAgent, agentProfilePath);
114
- writeFile(codexAgent, `---\nname: "gos-${agent.id}"\ndescription: "${safeDesc}"\nmodel: inherit\ntools:\n - Read\n - Glob\n - Grep\n - Bash\n - Edit\n - Write\n---\n\nFonte canonica: \`${codexAgentTarget}\`\nLeia e siga o perfil em \`${codexAgentTarget}\`.`);
156
+ writeFile(codexAgent, `---\nname: "${aSlug}"\ndescription: "${safeDesc}"\nmodel: inherit\ntools:\n - Read\n - Glob\n - Grep\n - Bash\n - Edit\n - Write\n---\n\nFonte canonica: \`${codexAgentTarget}\`\nLeia e siga o perfil em \`${codexAgentTarget}\`.`);
157
+
158
+ // Codex / Antigravity (.agent/) / Gemini / Opencode usam namespace plano gos-<slug> em skills/.
159
+ // Agents aparecem no mesmo picker se emitidos como wrapper SKILL.md em skills/.
160
+ for (const ide of ['.codex', '.agent', '.gemini', '.opencode']) {
161
+ const ideAgentSkill = path.join(root, ide, 'skills', aSlug, 'SKILL.md');
162
+ writeFile(ideAgentSkill, skillWrapper(agent.id, relativeTarget(ideAgentSkill, agentProfilePath), agentDesc));
163
+ }
164
+
165
+ // Antigravity workflows (.agent/workflows/<id>.md) — slash command invocation no picker.
166
+ const antigravityWorkflow = path.join(root, '.agent', 'workflows', `${agent.id}.md`);
167
+ const workflowTarget = relativeTarget(antigravityWorkflow, agentProfilePath);
168
+ const safeAgentDesc = agentDesc.replace(/"/g, '\\"').replace(/\n/g, ' ').slice(0, 200);
169
+ writeFile(
170
+ antigravityWorkflow,
171
+ `---\ndescription: "${safeAgentDesc}"\n---\n\n# /${agent.id} (Antigravity Workflow)\n\nFonte canonica: \`${workflowTarget}\`\n\nLeia o arquivo canonico apontado em CANONICAL-SOURCE e execute as instrucoes como fonte primaria.\n\nCANONICAL-SOURCE: ${workflowTarget}\n\nArgumentos do usuario seguem o prompt do agente.`
172
+ );
115
173
  }
116
174
 
117
175
  for (const skill of skills) {
@@ -119,8 +177,7 @@ function main() {
119
177
  const canonicalPath = path.join(root, '.gos', skillTargetPath);
120
178
  const claudeSkill = path.join(root, '.claude', 'commands', 'gos', 'skills', `${skill.slug}.md`);
121
179
  const codexSkill = path.join(root, '.codex', 'skills', `gos-${skill.slug}`, 'SKILL.md');
122
- const codexSkillCmd = path.join(root, '.codex', 'commands', 'gos', 'skills', `${skill.slug}.md`);
123
- const antigravitySkill = path.join(root, '.antigravity', 'skills', `gos-${skill.slug}`, 'SKILL.md');
180
+ const antigravitySkill = path.join(root, '.agent', 'skills', `gos-${skill.slug}`, 'SKILL.md');
124
181
  const geminiSkill = path.join(root, '.gemini', 'skills', `gos-${skill.slug}`, 'SKILL.md');
125
182
  const opencodeSkill = path.join(root, '.opencode', 'skills', `gos-${skill.slug}`, 'SKILL.md');
126
183
  const qwenSkill = path.join(root, '.qwen', 'skills', `gos-${skill.slug}`, 'SKILL.md');
@@ -131,7 +188,6 @@ function main() {
131
188
  const skillDesc = skillFm.description || skill.description || skill.name || skill.slug;
132
189
  const skillArgHint = skillFm['argument-hint'] || '';
133
190
 
134
- writeFile(claudeSkill, skillWrapper(skill.slug, relativeTarget(claudeSkill, canonicalPath), skillDesc));
135
191
  writeFile(codexSkill, skillWrapper(skill.slug, relativeTarget(codexSkill, canonicalPath), skillDesc));
136
192
  writeFile(antigravitySkill, skillWrapper(skill.slug, relativeTarget(antigravitySkill, canonicalPath), skillDesc));
137
193
  writeFile(geminiSkill, skillWrapper(skill.slug, relativeTarget(geminiSkill, canonicalPath), skillDesc));
@@ -139,46 +195,13 @@ function main() {
139
195
  writeFile(qwenSkill, skillWrapper(skill.slug, relativeTarget(qwenSkill, canonicalPath), skillDesc));
140
196
 
141
197
  writeFile(qwenCmd, qwenCommandWrapper(`gos-${skill.slug}`, skillDesc, relativeTarget(qwenCmd, canonicalPath)));
142
- writeFile(codexSkillCmd, claudeCommandWrapper(`gos-${skill.slug}`, skillDesc, relativeTarget(codexSkillCmd, canonicalPath), skillArgHint));
143
- writeFile(claudeSkill, claudeCommandWrapper(`gos-${skill.slug}`, skillDesc, relativeTarget(claudeSkill, canonicalPath), skillArgHint));
198
+ writeFile(claudeSkill, claudeCommandWrapper(`gos-${skill.slug}`, skillDesc, relativeTarget(claudeSkill, canonicalPath), skillArgHint, 'Claude'));
144
199
  }
145
200
 
146
- const antigravityInstructions = [
147
- '# G-OS Antigravity Instructions',
148
- '',
149
- 'Leia sempre:',
150
- '- `AGENTS.md`',
151
- '- `CLAUDE.md`',
152
- '- `.gos/docs/toolchain-map.md`',
153
- '',
154
- 'Agentes disponiveis:',
155
- ...agents.map((agent) => `- ${agent.id}`),
156
- '',
157
- 'Skills curadas:',
158
- ...skills.map((skill) => `- ${skill.slug}`),
159
- '',
160
- '## Como invocar Skills',
161
- '',
162
- 'Para usar uma skill, leia o arquivo canonico e siga suas instrucoes.',
163
- 'Skills tambem disponiveis como adapters em `.antigravity/skills/`.',
164
- '',
165
- '| Skill | Arquivo canonico |',
166
- '|-------|-----------------|',
167
- ...skills.map((skill) => `| \`gos-${skill.slug}\` | \`.gos/${skill.skillFile || skill.path}\` |`)
168
- ].join('\n');
169
-
170
- writeFile(path.join(root, '.antigravity', 'instructions.md'), antigravityInstructions);
171
- writeFile(
172
- path.join(root, '.antigravity', 'config.json'),
173
- JSON.stringify(
174
- {
175
- project: 'g-os',
176
- instructions: ['instructions.md', '../AGENTS.md', '../CLAUDE.md']
177
- },
178
- null,
179
- 2
180
- )
181
- );
201
+ // Antigravity le AGENTS.md no root do workspace (ja existe no projeto).
202
+ // Skills vivem em .agent/skills/<slug>/SKILL.md (workspace scope, registrado acima no loop).
203
+ // Workflows (.agent/workflows/<id>.md) sao slash commands no picker da IDE.
204
+ // Rules opcionais em .agent/rules/ — nao geramos automaticamente (regras vivem no AGENTS.md).
182
205
 
183
206
  // Codex IDE Extension — AGENTS.md + config.toml
184
207
  // Codex e o ambiente de EXECUCAO (Opus planeja, Codex executa). Bloco abaixo garante
@@ -217,9 +240,9 @@ function main() {
217
240
  '',
218
241
  '## Como o Codex consome',
219
242
  '',
220
- '- Slash commands em `.codex/commands/gos/{agents,skills}/<id>.md` -> Codex carrega o canonico apontado em CANONICAL-SOURCE e executa.',
221
- '- Subagents em `.codex/agents/gos-<id>.md` -> referencia o profile em `.gos/agents/profiles/`.',
222
- '- Skills em `.codex/skills/gos-<slug>/SKILL.md` -> wrapper fino que aponta para `.gos/skills/<slug>/SKILL.md`.',
243
+ '- Slash commands em `.codex/commands/gos/{agents,skills}/<id>.md` -> unica superficie de skills/agents. Codex carrega o canonico apontado em CANONICAL-SOURCE e executa.',
244
+ '- Subagents em `.codex/agents/gos-<id>.md` -> declaracao de subagent (acessivel via Task tool e delegacao interna).',
245
+ '- Para invocar o orquestrador master, digite `/gos:agents:gos-master` no picker.',
223
246
  ''
224
247
  ].join('\n');
225
248
  writeFile(path.join(root, '.codex', 'AGENTS.md'), codexAgentsMd);
@@ -229,14 +252,7 @@ function main() {
229
252
  '# Edite os arquivos canonicos em .gos/ ao inves deste.',
230
253
  '',
231
254
  'project = "g-os"',
232
- '',
233
- '[instructions]',
234
- 'files = [',
235
- ' "AGENTS.md",',
236
- ' "../AGENTS.md",',
237
- ' "../CLAUDE.md",',
238
- ' "../.gos/docs/toolchain-map.md",',
239
- ']',
255
+ 'instructions = "AGENTS.md"',
240
256
  '',
241
257
  '[execution]',
242
258
  'primary_command = "*execute-plan"',
@@ -251,16 +267,14 @@ function main() {
251
267
  // Evita regressoes silenciosas que ja quebraram a IDE no passado.
252
268
  const codexFailures = [];
253
269
  for (const agent of agents) {
254
- const expectedAgent = path.join(root, '.codex', 'agents', `gos-${agent.id}.md`);
270
+ const expectedAgent = path.join(root, '.codex', 'agents', `${agentSlug(agent.id)}.md`);
255
271
  const expectedCmd = path.join(root, '.codex', 'commands', 'gos', 'agents', `${agent.id}.md`);
256
272
  if (!fs.existsSync(expectedAgent)) codexFailures.push(expectedAgent);
257
273
  if (!fs.existsSync(expectedCmd)) codexFailures.push(expectedCmd);
258
274
  }
259
275
  for (const skill of skills) {
260
276
  const expectedSkill = path.join(root, '.codex', 'skills', `gos-${skill.slug}`, 'SKILL.md');
261
- const expectedCmd = path.join(root, '.codex', 'commands', 'gos', 'skills', `${skill.slug}.md`);
262
277
  if (!fs.existsSync(expectedSkill)) codexFailures.push(expectedSkill);
263
- if (!fs.existsSync(expectedCmd)) codexFailures.push(expectedCmd);
264
278
  }
265
279
  if (!fs.existsSync(path.join(root, '.codex', 'AGENTS.md'))) codexFailures.push('.codex/AGENTS.md');
266
280
  if (!fs.existsSync(path.join(root, '.codex', 'config.toml'))) codexFailures.push('.codex/config.toml');
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: execute-plan
3
- description: Executa um plano (PLAN-NNN-<slug>) task-a-task aplicando state machine + visual gate contra Storybook canonico antes de marcar validacao. Comando primario do ambiente Codex IDE.
4
- argument-hint: "<PLAN-NNN-slug> [--task T-NNN-NN] [--skip-visual-gate]"
3
+ description: Executa um plano (PLAN-NNN-<slug>) task-a-task aplicando state machine + visual gate contra Storybook canonico antes de marcar validacao. Non-blocking em backend gaps (abre tasks ClickUp + segue). Comando primario do ambiente Codex IDE.
4
+ argument-hint: "<PLAN-NNN-slug> [--task T-NNN-NN] [--skip-visual-gate] [--skip-clickup]"
5
5
  allowedTools: [Read, Glob, Grep, Bash, Write, Edit, Agent, AskUserQuestion]
6
6
  sourceDocs:
7
7
  - templates/taskTemplate.md
@@ -35,11 +35,16 @@ Formato esperado:
35
35
 
36
36
  1. Resolver paths via `.gos-local/plan-paths.json`. Se ausente, abortar e instruir o usuario a rodar `*plan` primeiro.
37
37
  2. Localizar `<dirs.planos>/<PLAN-NNN-slug>/plan.md`. Se ausente, abortar.
38
- 3. Ler `plan.md` por completo: frontmatter + Componentes mapeados + Componentes ausentes + Aderencia a stack + Plano de execucao + Checklist de aceite.
38
+ 3. Ler `plan.md` por completo: frontmatter + Componentes mapeados + Componentes ausentes + Aderencia a stack + Plano de execucao + Checklist de aceite + **Backend pendings**.
39
39
  4. Validar `stack_ref` do frontmatter contra `<dirs.stack>` (`docs/stack.md`):
40
40
  - Calcular sha-curto atual e comparar.
41
41
  - Drift detectado: ABORTAR e instruir `*stack drift` + replanejar com `*stack refresh`.
42
42
  5. Ler `<dirs.progress>` (progress.txt). Se aponta para outro plano ativo, perguntar se troca o foco antes de prosseguir.
43
+ 6. **Backend pendings (non-blocking)**: ler tabela `## Backend pendings` do `plan.md`. Para cada linha:
44
+ - Sem `ClickUp ID` -> criar task via `mcp__clickup__clickup_create_task` (assignee `112010775` salvo override `ASSIGNEE` no plano, list de `clickup.backend_list_id` em `plan-paths.json`, titulo `[Backend] PLAN-NNN: <gap>`). Gravar ID retornado de volta na coluna `ClickUp ID` do `plan.md`.
45
+ - Com `ClickUp ID` -> consultar `mcp__clickup__clickup_get_task <ID>`. Atualizar coluna `Status`. Se ainda aberta, registrar em `progress.txt` como `blockers=<T-IDs>:<ClickUp-ID>:<gap-curto>` (uma linha por bloqueio ativo).
46
+ - `--skip-clickup` desliga MCP; nesse caso so registra warning visivel mantendo o que ja esta no plano.
47
+ - **Importante**: backend gap aberto NAO aborta o execute-plan. Tasks frontend que dependem do gap serao classificadas como `bloqueada-backend` no loop abaixo; tasks sem dependencia seguem normal.
43
48
 
44
49
  ## Pre-flight visual
45
50
 
@@ -52,11 +57,41 @@ Formato esperado:
52
57
  - Se ausente: gerar task de criacao do componente ANTES das tasks de implementacao. Renumerar `seq` das tasks restantes.
53
58
  4. Output do pre-flight: bloco em `progress.txt` campo `notes=` com numero de stories indexadas e tasks de criacao geradas.
54
59
 
60
+ ## Pre-flight visual smoke (NOVO)
61
+
62
+ Antes da T-01 (depois do pre-flight de stories acima), gera comparacao visual entre pagina renderizada e frame Figma para capturar gaps grandes ANTES da execucao — evita o padrao PLAN-005 onde feedback iterativo gerou 26 rodadas no fim.
63
+
64
+ Ativacao:
65
+ - Storybook disponivel: usa `.stories.tsx` da pagina-completa quando existe (ex.: `ProjetosPage.stories.tsx`) e renderiza via `npm run storybook -- --static-build` ou screenshot ja gerado em CI.
66
+ - Playwright MCP disponivel (`mcp__plugin_playwright_playwright__*`): navega para localhost (rota da pagina + seed declarado em `## Mock strategy` ou seed nativo do projeto) e captura screenshot.
67
+ - Nenhum disponivel: pula com warning em `progress.txt` (`smoke=skipped: no storybook story for full page nor playwright MCP`). NAO bloqueia.
68
+
69
+ Comparacao:
70
+ 1. Carrega frame Figma principal (`figma_url` do plano) via Figma MCP — extrai layout/secoes esperadas.
71
+ 2. Confronta screenshot vs frame em 3 dimensoes basicas (sem refazer 4-dim por componente — isso fica no gate por task):
72
+ - **Secoes presentes**: KPI row, toolbar, table, drawer trigger, etc. — cada secao esperada esta no screenshot?
73
+ - **Layout grosseiro**: ordem vertical das secoes; colunas da table batem com Figma?
74
+ - **Cores/tokens primarios**: bg da pagina, accent color, contrast — sem inversao obvia.
75
+ 3. Output: `<dirs.planos>/<PLAN-NNN-slug>/preflight-smoke.md` com lista de gaps detectados (secao faltando, layout invertido, cor errada).
76
+ 4. Se gaps detectados: gerar tasks `T-000-XX` (prefixo `000` = pre-flight) com `priority: P0` e prepend no inicio da fila. Renumerar `seq` das tasks subsequentes.
77
+
78
+ Pre-flight smoke nao substitui o visual gate por task — ele captura gaps grandes (componente faltando, KPI row ausente) que viraram tasks novas em PLAN-004/PLAN-005.
79
+
55
80
  ## Loop por task
56
81
 
57
- Iterar tasks em ordem de `seq`. Para cada `tasks/T-NNN-NN-*.md`:
82
+ Iterar tasks em ordem de `seq`. Antes de executar cada task, **classificar**:
58
83
 
59
- 1. **Mover para em-andamento**: `*progress status T-NNN-NN em-andamento`. State machine valida transicao.
84
+ - Ler frontmatter `depends_on_backend:` da task (campo opcional, default `[]`). Cada item referencia uma `gap-key` da tabela `## Backend pendings` do plano (ex.: `migration-20260501150000`, `endpoint-projetos-fields`).
85
+ - Se a task referencia gap em aberto (status no ClickUp != `concluido`/`closed`):
86
+ - `*progress status T-NNN-NN bloqueada-backend` (transicao livre desde `pendente` ou `em-andamento`).
87
+ - Anotar em `tasks/T-NNN-NN.notes.md` linha `## Bloqueada backend <iso>` com ClickUp IDs.
88
+ - **PULAR** task — NAO falha o loop, segue pra proxima.
89
+ - Se a task tem `depends_on_backend: []` ou todas as dependencias estao `concluido` no ClickUp -> seguir fluxo normal abaixo.
90
+
91
+ Para cada task **executavel**:
92
+
93
+ 0. **Pre-flight da task — gate de formato**: ler `tasks/T-NNN-NN*.md`. Confirmar que tem frontmatter YAML (`head -1 == "---"`) e contém `^status:` no frontmatter. Se ausente: ABORTAR a task com erro `task-malformada: T-NNN-NN sem frontmatter status — rodar plan-to-tasks regenerador OU migrate-task-status.js`. NUNCA tentar executar codigo sem frontmatter valido — sintoma do bug onde tasks ficam travadas em `pendente`.
94
+ 1. **Mover para em-andamento**: invocar `*progress status T-NNN-NN em-andamento` (skill `progress-tracker`). **Pos-condicao obrigatoria**: ler T-NNN-NN.md de novo e confirmar `^status: em-andamento$` no frontmatter. Se nao mudou: ABORTAR e instruir humano. NAO seguir para implementacao com status pendente — esse e o bug central reportado.
60
95
  2. **Despachar agent**: ler `labels: [agent:<slug>]` da task. Default: `dev`. Invocar via Agent tool com prompt completo (objetivo, plano de execucao, DoD, paths relevantes).
61
96
  3. **Implementacao**: agent edita arquivos seguindo o plano de execucao da task. Stack como contrato — nada fora de `docs/stack.md` salvo se `arch_change=true` no frontmatter do plano pai.
62
97
  4. **Visual gate** (antes de propor `validacao`):
@@ -64,29 +99,44 @@ Iterar tasks em ordem de `seq`. Para cada `tasks/T-NNN-NN-*.md`:
64
99
  Para cada componente alterado/criado pela task:
65
100
 
66
101
  a) Localizar `<Componente>.stories.tsx` em `<dirs.storybook>`.
67
- b) Comparar implementacao vs story canonica em 4 dimensoes textuais:
102
+ b) Comparar implementacao vs story canonica em 5 dimensoes textuais:
68
103
  - **Anatomia**: ordem de slots/elementos (header -> corpo -> footer; icones esquerda/direita; campos do form na ordem do design).
69
- - **Tokens**: classes Tailwind/variaveis CSS batem com DS (cor, raio, espacamento, tipografia).
70
- - **Variants**: props expostos cobrem variants da story.
71
- - **Densidade**: padding/gap dentro de +-1 step da escala do DS.
104
+ - **Tokens**: classes Tailwind/variaveis CSS batem com DS (cor, raio, espacamento, tipografia). Quando o componente aparece em `## Page-level overrides` do plano: a divergencia cosmetica registrada NAO falha o gate — o gate confirma que o override foi APLICADO conforme decisao (a/b/c).
105
+ - **Variants**: props expostos cobrem variants da story. Decisao (b) em `## Page-level overrides`: confirmar que a nova variant existe na story canonica.
106
+ - **Densidade**: padding/gap dentro de +-1 step da escala do DS (ou conforme override registrado).
107
+ - **Comportamentos** (NOVO, heuristico via JSX + grep, sem E2E):
108
+ - Para cada `interaction_target:` declarado no frontmatter da task: existe handler implementado no diff? (ex.: `row.onClick` chamando `openDrawer`; `button.onClick` chamando mutation; `form.onSubmit` chamando server action).
109
+ - Estados visuais (skeleton/empty/error/loading) declarados em `## Interações & Estados` renderizam? grep por `isLoading|isPending|isError|empty|skeleton` nos arquivos tocados.
110
+ - Refetch apos mutation: `invalidateQueries` / `router.refresh` / `setState` observavel no diff?
111
+ - Para cada `override_target:` declarado: classes/props da decisao (a/b/c) presentes no diff? grep das classes declaradas em `## Page-level overrides`.
72
112
  c) Para a tela como um todo: invocar Figma MCP no `figma_url` do plano e cruzar com o JSX renderizado em arvore (mesmo numero de secoes, mesma hierarquia, mesmas labels).
73
- d) Output: relatorio curto em `tasks/T-NNN-NN.notes.md` (4 secoes: anatomia, tokens, variants, densidade + secao "Arvore vs Figma").
74
- e) Divergencia >= 1 item critico (anatomia ou tokens) -> falha o gate.
113
+ d) Output: relatorio curto em `tasks/T-NNN-NN.notes.md` (5 secoes: anatomia, tokens, variants, densidade, comportamentos + secao "Arvore vs Figma").
114
+ e) Divergencia >= 1 item critico (anatomia, tokens nao-overrideados, ou comportamento mapeado sem implementacao) -> falha o gate.
75
115
 
76
116
  5. **Resultado do gate**:
77
- - Sucesso -> `*progress status T-NNN-NN validacao`. Preparar arquivos staged (sem commit).
117
+ - Sucesso -> invocar `*progress status T-NNN-NN validacao`. **Pos-condicao obrigatoria**: ler T-NNN-NN.md, confirmar `^status: validacao$` no frontmatter. Se nao mudou: ABORTAR a task com erro `progress-tracker-falhou: T-NNN-NN`. Preparar arquivos staged (sem commit) somente apos confirmacao.
78
118
  - Falha -> manter em `em-andamento`, gravar diff em `T-NNN-NN.notes.md`, retornar pra etapa 3.
79
119
 
120
+ **Invariante por task**: ao terminar a iteracao, T-NNN-NN.md DEVE ter status diferente de `pendente`. Se ainda for `pendente` apos a iteracao, registrar como bug do executor em `progress.txt` (`notes=executor-skipped-progress: T-NNN-NN`) e abortar o loop — sintoma do bug original.
121
+
80
122
  ## Fechamento
81
123
 
82
- Quando todas as tasks atingirem `validacao` E o checklist de aceite do plano estiver marcado:
124
+ Quando o loop terminar (todas as tasks executaveis em `validacao` ou puladas em `bloqueada-backend`):
83
125
 
84
126
  1. Listar arquivos modificados via `git diff --name-only`.
85
- 2. Preparar commit (NAO push). Mensagem segue Conventional Commits + referencia ao `PLAN-NNN-slug`.
86
- 3. Resumo final ao usuario:
87
- - Path do plano + numero de tasks concluidas.
88
- - Lista de componentes que passaram/falharam o visual gate.
89
- - Comando exato para o humano marcar `concluido`: `*progress status T-NNN-NN concluido` (apos validacao humana + smoke E2E).
127
+ 2. Preparar commit (NAO push). Mensagem segue Conventional Commits + referencia ao `PLAN-NNN-slug`. Se houve tasks bloqueadas, citar na mensagem (ex.: `feat(checkout): PLAN-042 T-01..T-05 (T-06,T-07 bloqueadas backend CU-XXX)`).
128
+ 3. Resumo final ao usuario em 4 blocos:
129
+ ```
130
+ [execute-plan] PLAN-NNN-<slug>
131
+
132
+ tasks validacao: <N> (T-..., T-...)
133
+ bloqueada-backend: <K> (T-...:CU-..., ...)
134
+ backend pendings: <X> abertas no ClickUp / <Y> concluidas
135
+ visual gate: passou / falhou em (T-...)
136
+
137
+ proximo passo: *validate-plan PLAN-NNN-<slug>
138
+ ```
139
+ 4. Indicar que o turn seguinte e `*validate-plan PLAN-NNN-<slug>` (skill `validate-plan`, ambiente Opus 4.7). NAO marcar `concluido` automaticamente — `validate-plan` cuida disso.
90
140
 
91
141
  ## Ambiente Codex IDE — observacoes
92
142
 
@@ -98,8 +148,10 @@ Quando todas as tasks atingirem `validacao` E o checklist de aceite do plano est
98
148
 
99
149
  - **Visual gate nao e opcional** salvo `--skip-visual-gate` explicito. Skill nao silencia o gate.
100
150
  - **Sem push automatico**: commit fica preparado. Push e responsabilidade do humano.
101
- - **State machine inviolavel**: transicao `concluido` so apos humano validar + checklist.
102
- - **Storybook como contrato**: componente sem `.stories.tsx` em `<dirs.storybook>` bloqueia a task ate ser criado.
151
+ - **State machine inviolavel**: transicao `concluido` ocorre via `*validate-plan` (auto-marca quando passa). Rollback humano: `*progress status T-NNN-NN pendente --rollback`.
152
+ - **Non-blocking em backend gaps**: gap aberto no ClickUp NAO aborta. Tasks dependentes viram `bloqueada-backend`, demais seguem.
153
+ - **Storybook como contrato base; Figma da pagina vence em conflito cosmetico**: componente sem `.stories.tsx` em `<dirs.storybook>` bloqueia a task ate ser criado. Divergencia cosmetica entre story e Figma da pagina que ESTA registrada em `## Page-level overrides` do plano: gate confirma aplicacao do override (decisao a/b/c). Divergencia NAO registrada no plano: gate falha — voltar pra `*plan` (ou registrar override no plan.md antes de prosseguir).
154
+ - **Comportamento mapeado e vinculante**: `interaction_target:` declarado na task DEVE ter handler/estado implementado e observavel no diff. Caso contrario, gate falha mesmo se anatomia + tokens passarem.
103
155
 
104
156
  ## Model guidance
105
157