ganbatte-os 0.2.38 → 0.2.42
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.
- package/.gos/README.md +2 -6
- package/.gos/agents/profiles/ganbatte-os-master.md +91 -58
- package/.gos/agents/profiles/index.json +3 -1
- package/.gos/agents/profiles/perf-optimizer.md +10 -0
- package/.gos/agents/profiles/po.md +1 -1
- package/.gos/agents/profiles/security-auditor.md +11 -0
- package/.gos/agents/profiles/sm.md +4 -4
- package/.gos/agents/profiles/squad-creator.md +2 -2
- package/.gos/config.json +23 -2
- package/.gos/docs/curation.md +8 -6
- package/.gos/docs/gos_installation_guide.md +1 -1
- package/.gos/docs/plan-distribuicao-publica.md +2 -3
- package/.gos/docs/toolchain-map.md +2 -2
- package/.gos/libraries/component-reuse-gate.md +75 -0
- package/.gos/libraries/doc-sync-policy.md +31 -0
- package/.gos/libraries/lazy-dev-policy.md +38 -0
- package/.gos/libraries/lucide-icons-policy.md +1 -1
- package/.gos/libraries/performance-audit-playbook.md +54 -0
- package/.gos/libraries/security-audit-playbook.md +63 -0
- package/.gos/manifests/g-os-runtime-manifest.json +29 -8
- package/.gos/playbooks/audit-streaming-playbook.md +86 -0
- package/.gos/playbooks/plan-creation-playbook.md +3 -5
- package/.gos/prompts/01-search.md +2 -2
- package/.gos/prompts/03-tasks.md +4 -4
- package/.gos/prompts/05-reviews.md +1 -1
- package/.gos/scripts/cli/gos-cli.js +16 -0
- package/.gos/scripts/hooks/claude-stop-summary.js +0 -16
- package/.gos/scripts/integrations/check-plan.js +15 -5
- package/.gos/scripts/integrations/setup-ide-adapters.js +72 -75
- package/.gos/scripts/tools/model-router.js +122 -0
- package/.gos/skills/audit-screenshots/SKILL.md +195 -155
- package/.gos/skills/execute-plan/SKILL.md +37 -23
- package/.gos/skills/figma-print-diff/SKILL.md +5 -0
- package/.gos/skills/perf-review/SKILL.md +58 -0
- package/.gos/skills/plan-blueprint/SKILL.md +34 -26
- package/.gos/skills/plan-to-tasks/SKILL.md +13 -2
- package/.gos/skills/progress-tracker/SKILL.md +3 -3
- package/.gos/skills/registry.json +3 -4
- package/.gos/skills/security-review/SKILL.md +59 -0
- package/.gos/skills/simplify-review/SKILL.md +59 -0
- package/.gos/skills/validate-plan/SKILL.md +29 -30
- package/.gos/squads/code-quality/README.md +20 -0
- package/.gos/squads/code-quality/squad.yaml +32 -0
- package/.gos/squads/code-quality/workflows/wf-code-quality.yaml +19 -0
- package/.gos/squads/design-delivery/README.md +1 -1
- package/.gos/squads/design-delivery/squad.yaml +4 -3
- package/.gos/squads/design-delivery/workflows/wf-design-delivery.yaml +6 -6
- package/.gos/templates/planTemplate.md +35 -5
- package/.gos/templates/specTemplate.md +68 -0
- package/.gos/templates/taskTemplate.md +37 -10
- package/AGENTS.md +0 -2
- package/CLAUDE.md +25 -9
- package/GEMINI.md +3 -3
- package/LICENSE +1 -1
- package/README.md +6 -8
- package/package.json +2 -3
- package/.gos/docs/slack-notifications.md +0 -219
- package/.gos/playbooks/sprint-planner-playbook.md +0 -127
- package/.gos/scripts/hooks/post-commit-notify.js +0 -175
- package/.gos/scripts/tools/clickup-preprocess.js +0 -218
- package/.gos/scripts/tools/clickup.js +0 -1058
- package/.gos/scripts/tools/slack-notify.js +0 -271
- package/.gos/skills/clickup/SKILL.md +0 -151
- package/.gos/skills/slack-review/SKILL.md +0 -91
- package/.gos/skills/sprint-planner/SKILL.md +0 -434
- package/.gos/skills/weekly-update/CHANGELOG.md +0 -165
- package/.gos/skills/weekly-update/SKILL.md +0 -361
- package/.gos/squads/sprint-planning/agents/sprint-chief.md +0 -47
- package/.gos/squads/sprint-planning/agents/sprint-planner-agent.md +0 -43
- package/.gos/squads/sprint-planning/agents/sprint-tracker.md +0 -43
- package/.gos/squads/sprint-planning/agents/task-importer.md +0 -44
- package/.gos/squads/sprint-planning/checklists/sprint-readiness.md +0 -27
- package/.gos/squads/sprint-planning/config/config.yaml +0 -65
- package/.gos/squads/sprint-planning/data/clickup-field-mapping.yaml +0 -94
- package/.gos/squads/sprint-planning/squad.yaml +0 -52
- package/.gos/squads/sprint-planning/tasks/close-sprint.md +0 -43
- package/.gos/squads/sprint-planning/tasks/create-sprint.md +0 -42
- package/.gos/squads/sprint-planning/tasks/import-tasks.md +0 -39
- package/.gos/squads/sprint-planning/tasks/sync-status.md +0 -31
- package/.gos/squads/sprint-planning/workflows/wf-sprint-creation.yaml +0 -59
- package/.gos/squads/sprint-planning/workflows/wf-sprint-sync.yaml +0 -35
- package/.gos/templates/sprint-clickup.template.md +0 -80
|
@@ -26,37 +26,36 @@ function agentSlug(id) {
|
|
|
26
26
|
return id.startsWith('gos-') ? id : `gos-${id}`;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
}
|
|
29
|
+
// IDEs alvo do G-OS (adapters gerados para estas).
|
|
30
|
+
// Claude Code, Codex, Qwen, Opencode, Gemini CLI, Antigravity (.agent).
|
|
31
|
+
const TARGET_IDES = ['.claude', '.codex', '.qwen', '.opencode', '.gemini', '.agent'];
|
|
32
|
+
// Entrypoints user-facing (slash/workflow). O master roteia o resto internamente (item 10).
|
|
33
|
+
const CMD_WHITELIST = new Set(['gos-master', 'ux-design-expert']);
|
|
34
|
+
|
|
35
|
+
function removeGosPrefixed(dir) {
|
|
36
|
+
// Remove apenas entradas G-OS (prefixo gos-), preservando skills/agents/workflows
|
|
37
|
+
// proprios do usuario que coexistem no mesmo diretorio (contrato do prefixo gos-).
|
|
38
|
+
if (!fs.existsSync(dir)) return;
|
|
39
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
40
|
+
if (/^gos-/.test(entry)) {
|
|
41
|
+
fs.rmSync(path.join(dir, entry), { recursive: true, force: true });
|
|
51
42
|
}
|
|
52
43
|
}
|
|
44
|
+
}
|
|
53
45
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
46
|
+
function cleanupStaleAdapters() {
|
|
47
|
+
for (const ide of TARGET_IDES) {
|
|
48
|
+
removeGosPrefixed(path.join(root, ide, 'skills')); // gos-<slug>/
|
|
49
|
+
removeGosPrefixed(path.join(root, ide, 'agents')); // gos-<id>.md
|
|
50
|
+
// Commands sao namespaced sob gos/ — seguro remover o namespace inteiro.
|
|
51
|
+
const cmdGos = path.join(root, ide, 'commands', 'gos');
|
|
52
|
+
if (fs.existsSync(cmdGos)) fs.rmSync(cmdGos, { recursive: true, force: true });
|
|
59
53
|
}
|
|
54
|
+
// Antigravity: workflows dos entrypoints (gos-*) — limpar apenas os do G-OS.
|
|
55
|
+
removeGosPrefixed(path.join(root, '.agent', 'workflows'));
|
|
56
|
+
// .antigravity/ e o diretorio errado (Antigravity usa .agent/) — remover legado.
|
|
57
|
+
const legacyAntigravity = path.join(root, '.antigravity');
|
|
58
|
+
if (fs.existsSync(legacyAntigravity)) fs.rmSync(legacyAntigravity, { recursive: true, force: true });
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
function agentWrapper(agentId, target) {
|
|
@@ -131,71 +130,57 @@ function main() {
|
|
|
131
130
|
const agentProfilePath = path.join(root, '.gos', 'agents', 'profiles', agent.path);
|
|
132
131
|
const agentDesc = extractAgentDescription(agentProfilePath) || `${agent.id} agent`;
|
|
133
132
|
const aSlug = agentSlug(agent.id);
|
|
133
|
+
const safeDesc = agentDesc.replace(/"/g, '\\"').replace(/\n/g, ' ').slice(0, 200);
|
|
134
|
+
const isEntrypoint = CMD_WHITELIST.has(agent.id);
|
|
135
|
+
|
|
136
|
+
// Subagents (delegacao interna do master) — para TODOS os agents.
|
|
137
|
+
const qwenAgent = path.join(root, '.qwen', 'agents', `${aSlug}.md`);
|
|
138
|
+
const qwenAgentTarget = relativeTarget(qwenAgent, agentProfilePath);
|
|
139
|
+
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: \`${qwenAgentTarget}\`\nLeia e siga o perfil em \`${qwenAgentTarget}\`.`);
|
|
140
|
+
|
|
141
|
+
const codexAgent = path.join(root, '.codex', 'agents', `${aSlug}.md`);
|
|
142
|
+
const codexAgentTarget = relativeTarget(codexAgent, agentProfilePath);
|
|
143
|
+
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}\`.`);
|
|
144
|
+
|
|
145
|
+
// Slash commands / picker (user-facing) — APENAS entrypoints (item 10).
|
|
146
|
+
if (!isEntrypoint) continue;
|
|
134
147
|
|
|
135
|
-
// Claude commands (requires YAML frontmatter with description)
|
|
136
148
|
const claudeFile = path.join(root, '.claude', 'commands', 'gos', 'agents', `${agent.id}.md`);
|
|
137
149
|
writeFile(claudeFile, claudeCommandWrapper(aSlug, agentDesc, relativeTarget(claudeFile, agentProfilePath), '', 'Claude'));
|
|
138
150
|
|
|
139
|
-
// Qwen commands (requires YAML frontmatter with description)
|
|
140
151
|
const qwenCmd = path.join(root, '.qwen', 'commands', 'gos', 'agents', `${agent.id}.md`);
|
|
141
152
|
writeFile(qwenCmd, qwenCommandWrapper(aSlug, agentDesc, relativeTarget(qwenCmd, agentProfilePath)));
|
|
142
153
|
|
|
143
|
-
// Qwen sub-agents
|
|
144
|
-
const qwenAgent = path.join(root, '.qwen', 'agents', `${aSlug}.md`);
|
|
145
|
-
const agentTarget = relativeTarget(qwenAgent, agentProfilePath);
|
|
146
|
-
const safeDesc = agentDesc.replace(/"/g, '\\"').replace(/\n/g, ' ').slice(0, 200);
|
|
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
154
|
const codexCmd = path.join(root, '.codex', 'commands', 'gos', 'agents', `${agent.id}.md`);
|
|
151
155
|
writeFile(codexCmd, claudeCommandWrapper(aSlug, agentDesc, relativeTarget(codexCmd, agentProfilePath), '', 'Codex'));
|
|
152
156
|
|
|
153
|
-
// Codex
|
|
154
|
-
|
|
155
|
-
const
|
|
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']) {
|
|
157
|
+
// Codex/Opencode/Gemini/Antigravity populam o picker a partir de <ide>/skills/<slug>/SKILL.md.
|
|
158
|
+
// Emitir o entrypoint tambem como skill garante que aparece no picker de cada uma.
|
|
159
|
+
for (const ide of ['.codex', '.opencode', '.gemini', '.agent']) {
|
|
161
160
|
const ideAgentSkill = path.join(root, ide, 'skills', aSlug, 'SKILL.md');
|
|
162
161
|
writeFile(ideAgentSkill, skillWrapper(agent.id, relativeTarget(ideAgentSkill, agentProfilePath), agentDesc));
|
|
163
162
|
}
|
|
164
163
|
|
|
165
|
-
// Antigravity
|
|
166
|
-
const antigravityWorkflow = path.join(root, '.agent', 'workflows', `${
|
|
164
|
+
// Antigravity: workflow (slash command no picker) — nome prefixado gos- p/ coexistencia.
|
|
165
|
+
const antigravityWorkflow = path.join(root, '.agent', 'workflows', `${aSlug}.md`);
|
|
167
166
|
const workflowTarget = relativeTarget(antigravityWorkflow, agentProfilePath);
|
|
168
|
-
const safeAgentDesc = agentDesc.replace(/"/g, '\\"').replace(/\n/g, ' ').slice(0, 200);
|
|
169
167
|
writeFile(
|
|
170
168
|
antigravityWorkflow,
|
|
171
|
-
`---\ndescription: "${
|
|
169
|
+
`---\ndescription: "${safeDesc}"\n---\n\n# /${aSlug} (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
170
|
);
|
|
173
171
|
}
|
|
174
172
|
|
|
173
|
+
// Skills: auto-discoveraveis (o master as invoca internamente), NAO slash commands.
|
|
175
174
|
for (const skill of skills) {
|
|
176
175
|
const skillTargetPath = skill.skillFile || skill.path;
|
|
177
176
|
const canonicalPath = path.join(root, '.gos', skillTargetPath);
|
|
178
|
-
const claudeSkill = path.join(root, '.claude', 'commands', 'gos', 'skills', `${skill.slug}.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');
|
|
181
|
-
const geminiSkill = path.join(root, '.gemini', 'skills', `gos-${skill.slug}`, 'SKILL.md');
|
|
182
|
-
const opencodeSkill = path.join(root, '.opencode', 'skills', `gos-${skill.slug}`, 'SKILL.md');
|
|
183
|
-
const qwenSkill = path.join(root, '.qwen', 'skills', `gos-${skill.slug}`, 'SKILL.md');
|
|
184
|
-
|
|
185
|
-
const qwenCmd = path.join(root, '.qwen', 'commands', 'gos', 'skills', `${skill.slug}.md`);
|
|
186
|
-
|
|
187
177
|
const skillFm = parseFrontmatter(canonicalPath);
|
|
188
178
|
const skillDesc = skillFm.description || skill.description || skill.name || skill.slug;
|
|
189
|
-
const skillArgHint = skillFm['argument-hint'] || '';
|
|
190
|
-
|
|
191
|
-
writeFile(codexSkill, skillWrapper(skill.slug, relativeTarget(codexSkill, canonicalPath), skillDesc));
|
|
192
|
-
writeFile(antigravitySkill, skillWrapper(skill.slug, relativeTarget(antigravitySkill, canonicalPath), skillDesc));
|
|
193
|
-
writeFile(geminiSkill, skillWrapper(skill.slug, relativeTarget(geminiSkill, canonicalPath), skillDesc));
|
|
194
|
-
writeFile(opencodeSkill, skillWrapper(skill.slug, relativeTarget(opencodeSkill, canonicalPath), skillDesc));
|
|
195
|
-
writeFile(qwenSkill, skillWrapper(skill.slug, relativeTarget(qwenSkill, canonicalPath), skillDesc));
|
|
196
179
|
|
|
197
|
-
|
|
198
|
-
|
|
180
|
+
for (const ide of TARGET_IDES) {
|
|
181
|
+
const t = path.join(root, ide, 'skills', `gos-${skill.slug}`, 'SKILL.md');
|
|
182
|
+
writeFile(t, skillWrapper(skill.slug, relativeTarget(t, canonicalPath), skillDesc));
|
|
183
|
+
}
|
|
199
184
|
}
|
|
200
185
|
|
|
201
186
|
// Antigravity le AGENTS.md no root do workspace (ja existe no projeto).
|
|
@@ -228,11 +213,17 @@ function main() {
|
|
|
228
213
|
'Ciclo: pre-flight visual -> loop por task com state machine -> visual gate -> validacao -> humano marca concluido.',
|
|
229
214
|
'Detalhes: `../.gos/skills/execute-plan/SKILL.md`.',
|
|
230
215
|
'',
|
|
231
|
-
'##
|
|
216
|
+
'## Entrypoints (slash commands user-facing)',
|
|
217
|
+
'',
|
|
218
|
+
'Apenas 2 comandos: o master roteia o resto internamente (skills, subagents, squads).',
|
|
219
|
+
'',
|
|
220
|
+
...agents.filter((a) => CMD_WHITELIST.has(a.id)).map((agent) => `- \`/gos:agents:${agent.id}\` -> \`../.gos/agents/profiles/${agent.path}\``),
|
|
221
|
+
'',
|
|
222
|
+
'## Subagents (delegacao interna do master)',
|
|
232
223
|
'',
|
|
233
224
|
...agents.map((agent) => `- \`gos-${agent.id}\` -> \`../.gos/agents/profiles/${agent.path}\``),
|
|
234
225
|
'',
|
|
235
|
-
'## Skills curadas',
|
|
226
|
+
'## Skills curadas (auto-discoveraveis, invocadas pelo master)',
|
|
236
227
|
'',
|
|
237
228
|
'| Slug | Arquivo canonico |',
|
|
238
229
|
'|------|------------------|',
|
|
@@ -240,9 +231,10 @@ function main() {
|
|
|
240
231
|
'',
|
|
241
232
|
'## Como o Codex consome',
|
|
242
233
|
'',
|
|
243
|
-
'- Slash commands
|
|
244
|
-
'- Subagents em `.codex/agents/gos-<id>.md` ->
|
|
245
|
-
'-
|
|
234
|
+
'- Slash commands (`.codex/commands/gos/agents/`) apenas para os 2 entrypoints. O `gos-master` analisa o input e decide quais skills, agents, subagents e squads acionar.',
|
|
235
|
+
'- Subagents em `.codex/agents/gos-<id>.md` -> delegacao interna (Task tool).',
|
|
236
|
+
'- Skills em `.codex/skills/gos-<slug>/SKILL.md` -> auto-discoveraveis; o master as invoca, o usuario nao digita.',
|
|
237
|
+
'- Para comecar, digite `/gos:agents:gos-master` no picker.',
|
|
246
238
|
''
|
|
247
239
|
].join('\n');
|
|
248
240
|
writeFile(path.join(root, '.codex', 'AGENTS.md'), codexAgentsMd);
|
|
@@ -267,10 +259,14 @@ function main() {
|
|
|
267
259
|
// Evita regressoes silenciosas que ja quebraram a IDE no passado.
|
|
268
260
|
const codexFailures = [];
|
|
269
261
|
for (const agent of agents) {
|
|
262
|
+
// Subagent (delegacao) para TODOS os agents.
|
|
270
263
|
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
264
|
if (!fs.existsSync(expectedAgent)) codexFailures.push(expectedAgent);
|
|
273
|
-
|
|
265
|
+
// Slash command apenas para entrypoints.
|
|
266
|
+
if (CMD_WHITELIST.has(agent.id)) {
|
|
267
|
+
const expectedCmd = path.join(root, '.codex', 'commands', 'gos', 'agents', `${agent.id}.md`);
|
|
268
|
+
if (!fs.existsSync(expectedCmd)) codexFailures.push(expectedCmd);
|
|
269
|
+
}
|
|
274
270
|
}
|
|
275
271
|
for (const skill of skills) {
|
|
276
272
|
const expectedSkill = path.join(root, '.codex', 'skills', `gos-${skill.slug}`, 'SKILL.md');
|
|
@@ -284,8 +280,9 @@ function main() {
|
|
|
284
280
|
process.exit(1);
|
|
285
281
|
}
|
|
286
282
|
|
|
287
|
-
|
|
288
|
-
console.log(`
|
|
283
|
+
const entrypoints = agents.filter((a) => CMD_WHITELIST.has(a.id)).length;
|
|
284
|
+
console.log(`Adapters gerados: ${entrypoints} entrypoints (slash), ${agents.length} subagents, ${skills.length} skills.`);
|
|
285
|
+
console.log(`IDEs alvo: ${TARGET_IDES.join(', ')}.`);
|
|
289
286
|
}
|
|
290
287
|
|
|
291
288
|
main();
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* model-router — resolve modelo/provider por etapa do pipeline.
|
|
4
|
+
*
|
|
5
|
+
* Regra do framework: Junior executa, Senior audita.
|
|
6
|
+
* - plan -> Senior cria plano + tasks + spec
|
|
7
|
+
* - execute -> Junior (modelo mais barato adequado) implementa
|
|
8
|
+
* - validate -> Senior audita e corrige gaps
|
|
9
|
+
*
|
|
10
|
+
* Precedência: .gos-local/models.json (override local, por dev/projeto)
|
|
11
|
+
* -> .gos/config.json "stageModels" (default versionado).
|
|
12
|
+
*
|
|
13
|
+
* Uso (CLI):
|
|
14
|
+
* node model-router.js # imprime o mapa resolvido das 3 etapas
|
|
15
|
+
* node model-router.js get <stage> # imprime JSON da etapa (plan|execute|validate)
|
|
16
|
+
* node model-router.js model <stage> # imprime só o model da etapa
|
|
17
|
+
* node model-router.js init # cria .gos-local/models.json com os defaults
|
|
18
|
+
*
|
|
19
|
+
* Uso (módulo):
|
|
20
|
+
* const { resolveStage, resolveAll } = require('./model-router');
|
|
21
|
+
* const { provider, model, role } = resolveStage('plan');
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
|
|
29
|
+
const STAGES = ['plan', 'execute', 'validate'];
|
|
30
|
+
const LOCAL_FILE = '.gos-local/models.json';
|
|
31
|
+
|
|
32
|
+
// Fallback embutido — usado se config.json não tiver stageModels (ex.: instalação antiga).
|
|
33
|
+
const HARD_DEFAULTS = {
|
|
34
|
+
plan: { provider: 'anthropic', model: 'claude-opus-4-8', role: 'senior' },
|
|
35
|
+
execute: {
|
|
36
|
+
provider: 'any',
|
|
37
|
+
model: 'claude-sonnet-5',
|
|
38
|
+
role: 'junior',
|
|
39
|
+
allow: ['claude-sonnet-5', 'codex', 'claude-haiku-4-5', 'cheapest-capable'],
|
|
40
|
+
},
|
|
41
|
+
validate: { provider: 'anthropic', model: 'claude-opus-4-8', role: 'senior' },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function findRoot(start) {
|
|
45
|
+
let dir = start || process.cwd();
|
|
46
|
+
for (let i = 0; i < 8; i++) {
|
|
47
|
+
if (fs.existsSync(path.join(dir, '.gos', 'config.json'))) return dir;
|
|
48
|
+
const parent = path.dirname(dir);
|
|
49
|
+
if (parent === dir) break;
|
|
50
|
+
dir = parent;
|
|
51
|
+
}
|
|
52
|
+
return process.cwd();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readJson(file) {
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function defaultsFromConfig(root) {
|
|
64
|
+
const cfg = readJson(path.join(root, '.gos', 'config.json'));
|
|
65
|
+
const sm = cfg && cfg.stageModels;
|
|
66
|
+
if (!sm) return { ...HARD_DEFAULTS };
|
|
67
|
+
const out = {};
|
|
68
|
+
for (const stage of STAGES) out[stage] = sm[stage] || HARD_DEFAULTS[stage];
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolveAll(root) {
|
|
73
|
+
root = root || findRoot();
|
|
74
|
+
const base = defaultsFromConfig(root);
|
|
75
|
+
const local = readJson(path.join(root, LOCAL_FILE));
|
|
76
|
+
const overrides = local && local.stageModels ? local.stageModels : {};
|
|
77
|
+
const out = {};
|
|
78
|
+
for (const stage of STAGES) {
|
|
79
|
+
out[stage] = { ...base[stage], ...(overrides[stage] || {}) };
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function resolveStage(stage, root) {
|
|
85
|
+
if (!STAGES.includes(stage)) {
|
|
86
|
+
throw new Error(`Etapa desconhecida: ${stage}. Use: ${STAGES.join(', ')}`);
|
|
87
|
+
}
|
|
88
|
+
return resolveAll(root)[stage];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function writeLocalDefaults(root) {
|
|
92
|
+
root = root || findRoot();
|
|
93
|
+
const target = path.join(root, LOCAL_FILE);
|
|
94
|
+
if (fs.existsSync(target)) return { created: false, path: target };
|
|
95
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
96
|
+
const payload = {
|
|
97
|
+
schema: 'gos.models.v1',
|
|
98
|
+
stageModels: defaultsFromConfig(root),
|
|
99
|
+
};
|
|
100
|
+
fs.writeFileSync(target, JSON.stringify(payload, null, 2) + '\n');
|
|
101
|
+
return { created: true, path: target };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = { STAGES, resolveStage, resolveAll, writeLocalDefaults };
|
|
105
|
+
|
|
106
|
+
// --- CLI ---------------------------------------------------------------------
|
|
107
|
+
if (require.main === module) {
|
|
108
|
+
const [cmd, arg] = process.argv.slice(2);
|
|
109
|
+
const root = findRoot();
|
|
110
|
+
if (cmd === 'get') {
|
|
111
|
+
process.stdout.write(JSON.stringify(resolveStage(arg, root), null, 2) + '\n');
|
|
112
|
+
} else if (cmd === 'model') {
|
|
113
|
+
process.stdout.write((resolveStage(arg, root).model || '') + '\n');
|
|
114
|
+
} else if (cmd === 'init') {
|
|
115
|
+
const r = writeLocalDefaults(root);
|
|
116
|
+
process.stdout.write(
|
|
117
|
+
(r.created ? 'Criado: ' : 'Ja existe: ') + r.path + '\n'
|
|
118
|
+
);
|
|
119
|
+
} else {
|
|
120
|
+
process.stdout.write(JSON.stringify(resolveAll(root), null, 2) + '\n');
|
|
121
|
+
}
|
|
122
|
+
}
|