ganbatte-os 0.2.33 → 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,120 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-plan.js — barreira deterministica de criacao de plano.
4
+ *
5
+ * Roda como ULTIMA acao obrigatoria do *plan (apos plan-blueprint +
6
+ * plan-to-tasks). Valida que o plano e usavel pelo *execute-plan e
7
+ * *validate-plan. Falha = *plan nao terminou — usuario ve erro, nao
8
+ * "plano criado" ilusorio.
9
+ *
10
+ * Uso:
11
+ * node check-plan.js <plan-dir>
12
+ *
13
+ * Verificacoes:
14
+ * 1. plan.md existe e tem frontmatter YAML.
15
+ * 2. context.md existe.
16
+ * 3. tasks/ existe e contem >= 1 T-NN*.md.
17
+ * 4. Cada T-NN*.md: head -1 == "---" E frontmatter contem `status: pendente`
18
+ * E `id:`, `plan_id:`, `seq:`, `title:`.
19
+ * 5. Nenhum T-NN*.md tem secao `## Status` no body (formato bugado legado).
20
+ *
21
+ * Exit code:
22
+ * 0 = plano valido, usavel pelo pipeline
23
+ * 1 = falha — mensagem aponta exatamente o que esta errado
24
+ */
25
+
26
+ const fs = require('node:fs');
27
+ const path = require('node:path');
28
+
29
+ const planDir = process.argv[2];
30
+
31
+ if (!planDir) {
32
+ console.error('uso: check-plan.js <plan-dir>');
33
+ process.exit(1);
34
+ }
35
+
36
+ if (!fs.existsSync(planDir)) {
37
+ console.error(`[check-plan] FALHA: plan-dir nao existe: ${planDir}`);
38
+ process.exit(1);
39
+ }
40
+
41
+ const errors = [];
42
+ const warnings = [];
43
+
44
+ const planFile = path.join(planDir, 'plan.md');
45
+ const contextFile = path.join(planDir, 'context.md');
46
+ const tasksDir = path.join(planDir, 'tasks');
47
+
48
+ if (!fs.existsSync(planFile)) {
49
+ errors.push(`plan.md ausente em ${planDir}`);
50
+ } else {
51
+ const plan = fs.readFileSync(planFile, 'utf8');
52
+ if (!plan.startsWith('---\n')) {
53
+ errors.push(`plan.md sem frontmatter YAML (head -1 != "---")`);
54
+ }
55
+ }
56
+
57
+ if (!fs.existsSync(contextFile)) {
58
+ warnings.push(`context.md ausente — recomendado, nao bloqueia`);
59
+ }
60
+
61
+ if (!fs.existsSync(tasksDir)) {
62
+ errors.push(`tasks/ ausente em ${planDir} — *plan deve invocar plan-to-tasks`);
63
+ } else {
64
+ const taskFiles = fs.readdirSync(tasksDir)
65
+ .filter((f) => /^T-\d+.*\.md$/.test(f))
66
+ .map((f) => path.join(tasksDir, f));
67
+
68
+ if (taskFiles.length === 0) {
69
+ errors.push(`tasks/ vazio — nenhum T-NN*.md encontrado`);
70
+ }
71
+
72
+ for (const file of taskFiles) {
73
+ const rel = path.relative(planDir, file);
74
+ const raw = fs.readFileSync(file, 'utf8');
75
+
76
+ if (!raw.startsWith('---\n')) {
77
+ errors.push(`${rel}: sem frontmatter YAML (head -1 != "---")`);
78
+ continue;
79
+ }
80
+
81
+ const fmEnd = raw.indexOf('\n---', 4);
82
+ if (fmEnd === -1) {
83
+ errors.push(`${rel}: frontmatter aberto sem fechar ("---" final ausente)`);
84
+ continue;
85
+ }
86
+ const frontmatter = raw.slice(4, fmEnd);
87
+ const body = raw.slice(fmEnd + 4);
88
+
89
+ const required = ['id', 'plan_id', 'seq', 'title', 'status'];
90
+ for (const key of required) {
91
+ if (!new RegExp(`^${key}:\\s*\\S`, 'm').test(frontmatter)) {
92
+ errors.push(`${rel}: frontmatter sem campo \`${key}:\``);
93
+ }
94
+ }
95
+
96
+ if (!/^status:\s*pendente\s*$/m.test(frontmatter)) {
97
+ errors.push(`${rel}: frontmatter status: deve ser exatamente "pendente" no plano recem-criado`);
98
+ }
99
+
100
+ if (/^## Status\s*$/m.test(body)) {
101
+ errors.push(`${rel}: contem secao "## Status" no body (formato legado bugado) — rodar migrate-task-status.js`);
102
+ }
103
+ }
104
+ }
105
+
106
+ if (warnings.length) {
107
+ for (const w of warnings) console.error(`[check-plan] aviso: ${w}`);
108
+ }
109
+
110
+ if (errors.length) {
111
+ console.error(`\n[check-plan] FALHA — plano NAO usavel pelo *execute-plan:\n`);
112
+ for (const e of errors) console.error(` - ${e}`);
113
+ console.error(`\nproximo passo: regerar tasks via plan-to-tasks OU rodar migrate-task-status.js ${planDir}`);
114
+ process.exit(1);
115
+ }
116
+
117
+ const taskCount = fs.readdirSync(tasksDir)
118
+ .filter((f) => /^T-\d+.*\.md$/.test(f)).length;
119
+ console.log(`[check-plan] OK — ${planDir}: plan.md + context.md + ${taskCount} tasks (todas com frontmatter status: pendente).`);
120
+ process.exit(0);
@@ -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,31 +125,59 @@ 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}\`.`);
148
+
149
+ // Codex commands (slash commands no Codex IDE Extension)
150
+ const codexCmd = path.join(root, '.codex', 'commands', 'gos', 'agents', `${agent.id}.md`);
151
+ writeFile(codexCmd, claudeCommandWrapper(aSlug, agentDesc, relativeTarget(codexCmd, agentProfilePath), '', 'Codex'));
152
+
153
+ // Codex sub-agents (Codex IDE espera .codex/agents/<id>.md)
154
+ const codexAgent = path.join(root, '.codex', 'agents', `${aSlug}.md`);
155
+ const codexAgentTarget = relativeTarget(codexAgent, agentProfilePath);
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
+ );
106
173
  }
107
174
 
108
175
  for (const skill of skills) {
109
176
  const skillTargetPath = skill.skillFile || skill.path;
110
177
  const canonicalPath = path.join(root, '.gos', skillTargetPath);
111
178
  const claudeSkill = path.join(root, '.claude', 'commands', 'gos', 'skills', `${skill.slug}.md`);
112
- const codexSkill = path.join(root, '.agents', 'skills', `gos-${skill.slug}`, 'SKILL.md');
113
- const antigravitySkill = path.join(root, '.antigravity', 'skills', `gos-${skill.slug}`, 'SKILL.md');
179
+ const codexSkill = path.join(root, '.codex', 'skills', `gos-${skill.slug}`, 'SKILL.md');
180
+ const antigravitySkill = path.join(root, '.agent', 'skills', `gos-${skill.slug}`, 'SKILL.md');
114
181
  const geminiSkill = path.join(root, '.gemini', 'skills', `gos-${skill.slug}`, 'SKILL.md');
115
182
  const opencodeSkill = path.join(root, '.opencode', 'skills', `gos-${skill.slug}`, 'SKILL.md');
116
183
  const qwenSkill = path.join(root, '.qwen', 'skills', `gos-${skill.slug}`, 'SKILL.md');
@@ -121,7 +188,6 @@ function main() {
121
188
  const skillDesc = skillFm.description || skill.description || skill.name || skill.slug;
122
189
  const skillArgHint = skillFm['argument-hint'] || '';
123
190
 
124
- writeFile(claudeSkill, skillWrapper(skill.slug, relativeTarget(claudeSkill, canonicalPath), skillDesc));
125
191
  writeFile(codexSkill, skillWrapper(skill.slug, relativeTarget(codexSkill, canonicalPath), skillDesc));
126
192
  writeFile(antigravitySkill, skillWrapper(skill.slug, relativeTarget(antigravitySkill, canonicalPath), skillDesc));
127
193
  writeFile(geminiSkill, skillWrapper(skill.slug, relativeTarget(geminiSkill, canonicalPath), skillDesc));
@@ -129,47 +195,97 @@ function main() {
129
195
  writeFile(qwenSkill, skillWrapper(skill.slug, relativeTarget(qwenSkill, canonicalPath), skillDesc));
130
196
 
131
197
  writeFile(qwenCmd, qwenCommandWrapper(`gos-${skill.slug}`, skillDesc, relativeTarget(qwenCmd, canonicalPath)));
132
- writeFile(claudeSkill, claudeCommandWrapper(`gos-${skill.slug}`, skillDesc, relativeTarget(claudeSkill, canonicalPath), skillArgHint));
198
+ writeFile(claudeSkill, claudeCommandWrapper(`gos-${skill.slug}`, skillDesc, relativeTarget(claudeSkill, canonicalPath), skillArgHint, 'Claude'));
133
199
  }
134
200
 
135
- const antigravityInstructions = [
136
- '# G-OS Antigravity Instructions',
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).
205
+
206
+ // Codex IDE Extension — AGENTS.md + config.toml
207
+ // Codex e o ambiente de EXECUCAO (Opus planeja, Codex executa). Bloco abaixo garante
208
+ // que slash commands e subagents estao disponiveis ao abrir o projeto no Codex.
209
+ const codexAgentsMd = [
210
+ '# G-OS no Codex IDE Extension',
211
+ '',
212
+ 'Este arquivo e auto-gerado por `npm run sync:ides`. Nao edite a mao.',
213
+ '',
214
+ 'Codex IDE Extension e o ambiente de EXECUCAO do G-OS. Opus 4.7 planeja em outra IDE/sessao;',
215
+ 'Codex executa task-a-task com `*execute-plan`.',
137
216
  '',
138
217
  'Leia sempre:',
139
- '- `AGENTS.md`',
140
- '- `CLAUDE.md`',
141
- '- `.gos/docs/toolchain-map.md`',
218
+ '- `../AGENTS.md` (raiz do projeto)',
219
+ '- `../CLAUDE.md`',
220
+ '- `../.gos/docs/toolchain-map.md`',
221
+ '',
222
+ '## Execucao de planos (comando primario do Codex)',
223
+ '',
224
+ '```',
225
+ '*execute-plan PLAN-NNN-<slug>',
226
+ '```',
227
+ '',
228
+ 'Ciclo: pre-flight visual -> loop por task com state machine -> visual gate -> validacao -> humano marca concluido.',
229
+ 'Detalhes: `../.gos/skills/execute-plan/SKILL.md`.',
230
+ '',
231
+ '## Agents disponiveis',
232
+ '',
233
+ ...agents.map((agent) => `- \`gos-${agent.id}\` -> \`../.gos/agents/profiles/${agent.path}\``),
142
234
  '',
143
- 'Agentes disponiveis:',
144
- ...agents.map((agent) => `- ${agent.id}`),
235
+ '## Skills curadas',
145
236
  '',
146
- 'Skills curadas:',
147
- ...skills.map((skill) => `- ${skill.slug}`),
237
+ '| Slug | Arquivo canonico |',
238
+ '|------|------------------|',
239
+ ...skills.map((skill) => `| \`gos-${skill.slug}\` | \`../.gos/${skill.skillFile || skill.path}\` |`),
148
240
  '',
149
- '## Como invocar Skills',
241
+ '## Como o Codex consome',
150
242
  '',
151
- 'Para usar uma skill, leia o arquivo canonico e siga suas instrucoes.',
152
- 'Skills tambem disponiveis como adapters em `.antigravity/skills/`.',
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.',
246
+ ''
247
+ ].join('\n');
248
+ writeFile(path.join(root, '.codex', 'AGENTS.md'), codexAgentsMd);
249
+
250
+ const codexConfigToml = [
251
+ '# G-OS Codex IDE Extension config (auto-gerado por npm run sync:ides).',
252
+ '# Edite os arquivos canonicos em .gos/ ao inves deste.',
153
253
  '',
154
- '| Skill | Arquivo canonico |',
155
- '|-------|-----------------|',
156
- ...skills.map((skill) => `| \`gos-${skill.slug}\` | \`.gos/${skill.skillFile || skill.path}\` |`)
254
+ 'project = "g-os"',
255
+ 'instructions = "AGENTS.md"',
256
+ '',
257
+ '[execution]',
258
+ 'primary_command = "*execute-plan"',
259
+ 'planning_command = "*plan"',
260
+ 'progress_command = "*progress"',
261
+ 'stack_command = "*stack"',
262
+ ''
157
263
  ].join('\n');
264
+ writeFile(path.join(root, '.codex', 'config.toml'), codexConfigToml);
158
265
 
159
- writeFile(path.join(root, '.antigravity', 'instructions.md'), antigravityInstructions);
160
- writeFile(
161
- path.join(root, '.antigravity', 'config.json'),
162
- JSON.stringify(
163
- {
164
- project: 'g-os',
165
- instructions: ['instructions.md', '../AGENTS.md', '../CLAUDE.md']
166
- },
167
- null,
168
- 2
169
- )
170
- );
266
+ // Validacao final: garantir que os arquivos do Codex foram gerados.
267
+ // Evita regressoes silenciosas que ja quebraram a IDE no passado.
268
+ const codexFailures = [];
269
+ for (const agent of agents) {
270
+ const expectedAgent = path.join(root, '.codex', 'agents', `${agentSlug(agent.id)}.md`);
271
+ const expectedCmd = path.join(root, '.codex', 'commands', 'gos', 'agents', `${agent.id}.md`);
272
+ if (!fs.existsSync(expectedAgent)) codexFailures.push(expectedAgent);
273
+ if (!fs.existsSync(expectedCmd)) codexFailures.push(expectedCmd);
274
+ }
275
+ for (const skill of skills) {
276
+ const expectedSkill = path.join(root, '.codex', 'skills', `gos-${skill.slug}`, 'SKILL.md');
277
+ if (!fs.existsSync(expectedSkill)) codexFailures.push(expectedSkill);
278
+ }
279
+ if (!fs.existsSync(path.join(root, '.codex', 'AGENTS.md'))) codexFailures.push('.codex/AGENTS.md');
280
+ if (!fs.existsSync(path.join(root, '.codex', 'config.toml'))) codexFailures.push('.codex/config.toml');
281
+ if (codexFailures.length > 0) {
282
+ console.error(`[sync:ides] Codex IDE adapters incompletos. Faltando ${codexFailures.length} arquivos:`);
283
+ for (const f of codexFailures) console.error(` - ${path.relative(root, f)}`);
284
+ process.exit(1);
285
+ }
171
286
 
172
287
  console.log(`Adapters generated for ${agents.length} agents and ${skills.length} skills.`);
288
+ console.log(`Codex IDE: ${agents.length} agents + ${skills.length} skills + AGENTS.md + config.toml.`);
173
289
  }
174
290
 
175
291
  main();