From 77b684e1c2f723483ff3280b01be66e965b3ac1e Mon Sep 17 00:00:00 2001 From: Malik Salim Date: Sun, 22 Mar 2026 19:29:34 -0400 Subject: [PATCH] fix: add codex skill metadata for gstack skills --- .../skills/gstack-autoplan/agents/openai.yaml | 6 ++ .../gstack-benchmark/agents/openai.yaml | 6 ++ .../skills/gstack-browse/agents/openai.yaml | 6 ++ .../skills/gstack-canary/agents/openai.yaml | 6 ++ .../skills/gstack-careful/agents/openai.yaml | 6 ++ .agents/skills/gstack-cso/agents/openai.yaml | 6 ++ .../agents/openai.yaml | 6 ++ .../gstack-design-review/agents/openai.yaml | 6 ++ .../agents/openai.yaml | 6 ++ .../skills/gstack-freeze/agents/openai.yaml | 6 ++ .../skills/gstack-guard/agents/openai.yaml | 6 ++ .../gstack-investigate/agents/openai.yaml | 6 ++ .../gstack-land-and-deploy/agents/openai.yaml | 6 ++ .../gstack-office-hours/agents/openai.yaml | 6 ++ .../gstack-plan-ceo-review/agents/openai.yaml | 6 ++ .../agents/openai.yaml | 6 ++ .../gstack-plan-eng-review/agents/openai.yaml | 6 ++ .../skills/gstack-qa-only/agents/openai.yaml | 6 ++ .agents/skills/gstack-qa/agents/openai.yaml | 6 ++ .../skills/gstack-retro/agents/openai.yaml | 6 ++ .../skills/gstack-review/agents/openai.yaml | 6 ++ .../agents/openai.yaml | 6 ++ .../gstack-setup-deploy/agents/openai.yaml | 6 ++ .agents/skills/gstack-ship/agents/openai.yaml | 6 ++ .../skills/gstack-unfreeze/agents/openai.yaml | 6 ++ .../skills/gstack-upgrade/agents/openai.yaml | 6 ++ .agents/skills/gstack/agents/openai.yaml | 6 ++ agents/openai.yaml | 4 + scripts/gen-skill-docs.ts | 78 +++++++++++++------ test/gen-skill-docs.test.ts | 19 +++++ test/helpers/codex-session-runner.ts | 10 ++- 31 files changed, 250 insertions(+), 23 deletions(-) create mode 100644 .agents/skills/gstack-autoplan/agents/openai.yaml create mode 100644 .agents/skills/gstack-benchmark/agents/openai.yaml create mode 100644 .agents/skills/gstack-browse/agents/openai.yaml create mode 100644 .agents/skills/gstack-canary/agents/openai.yaml create mode 100644 .agents/skills/gstack-careful/agents/openai.yaml create mode 100644 .agents/skills/gstack-cso/agents/openai.yaml create mode 100644 .agents/skills/gstack-design-consultation/agents/openai.yaml create mode 100644 .agents/skills/gstack-design-review/agents/openai.yaml create mode 100644 .agents/skills/gstack-document-release/agents/openai.yaml create mode 100644 .agents/skills/gstack-freeze/agents/openai.yaml create mode 100644 .agents/skills/gstack-guard/agents/openai.yaml create mode 100644 .agents/skills/gstack-investigate/agents/openai.yaml create mode 100644 .agents/skills/gstack-land-and-deploy/agents/openai.yaml create mode 100644 .agents/skills/gstack-office-hours/agents/openai.yaml create mode 100644 .agents/skills/gstack-plan-ceo-review/agents/openai.yaml create mode 100644 .agents/skills/gstack-plan-design-review/agents/openai.yaml create mode 100644 .agents/skills/gstack-plan-eng-review/agents/openai.yaml create mode 100644 .agents/skills/gstack-qa-only/agents/openai.yaml create mode 100644 .agents/skills/gstack-qa/agents/openai.yaml create mode 100644 .agents/skills/gstack-retro/agents/openai.yaml create mode 100644 .agents/skills/gstack-review/agents/openai.yaml create mode 100644 .agents/skills/gstack-setup-browser-cookies/agents/openai.yaml create mode 100644 .agents/skills/gstack-setup-deploy/agents/openai.yaml create mode 100644 .agents/skills/gstack-ship/agents/openai.yaml create mode 100644 .agents/skills/gstack-unfreeze/agents/openai.yaml create mode 100644 .agents/skills/gstack-upgrade/agents/openai.yaml create mode 100644 .agents/skills/gstack/agents/openai.yaml create mode 100644 agents/openai.yaml diff --git a/.agents/skills/gstack-autoplan/agents/openai.yaml b/.agents/skills/gstack-autoplan/agents/openai.yaml new file mode 100644 index 000000000..28794c1a3 --- /dev/null +++ b/.agents/skills/gstack-autoplan/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-autoplan" + short_description: "Auto-review pipeline — reads the full CEO, design, and eng review skills from disk and runs them sequentially with..." + default_prompt: "Use gstack-autoplan for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-benchmark/agents/openai.yaml b/.agents/skills/gstack-benchmark/agents/openai.yaml new file mode 100644 index 000000000..4df54f31f --- /dev/null +++ b/.agents/skills/gstack-benchmark/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-benchmark" + short_description: "Performance regression detection using the browse daemon. Establishes baselines for page load times, Core Web..." + default_prompt: "Use gstack-benchmark for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-browse/agents/openai.yaml b/.agents/skills/gstack-browse/agents/openai.yaml new file mode 100644 index 000000000..851f80838 --- /dev/null +++ b/.agents/skills/gstack-browse/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-browse" + short_description: "Fast headless browser for QA testing and site dogfooding. Navigate any URL, interact with elements, verify page..." + default_prompt: "Use gstack-browse for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-canary/agents/openai.yaml b/.agents/skills/gstack-canary/agents/openai.yaml new file mode 100644 index 000000000..e51e42311 --- /dev/null +++ b/.agents/skills/gstack-canary/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-canary" + short_description: "Post-deploy canary monitoring. Watches the live app for console errors, performance regressions, and page failures..." + default_prompt: "Use gstack-canary for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-careful/agents/openai.yaml b/.agents/skills/gstack-careful/agents/openai.yaml new file mode 100644 index 000000000..f470fcaa7 --- /dev/null +++ b/.agents/skills/gstack-careful/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-careful" + short_description: "Safety guardrails for destructive commands. Warns before rm -rf, DROP TABLE, force-push, git reset --hard, kubectl..." + default_prompt: "Use gstack-careful for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-cso/agents/openai.yaml b/.agents/skills/gstack-cso/agents/openai.yaml new file mode 100644 index 000000000..d92248152 --- /dev/null +++ b/.agents/skills/gstack-cso/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-cso" + short_description: "Chief Security Officer mode. Performs OWASP Top 10 audit, STRIDE threat modeling, attack surface analysis, auth flow..." + default_prompt: "Use gstack-cso for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-design-consultation/agents/openai.yaml b/.agents/skills/gstack-design-consultation/agents/openai.yaml new file mode 100644 index 000000000..3af30a8a2 --- /dev/null +++ b/.agents/skills/gstack-design-consultation/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-design-consultation" + short_description: "Design consultation: understands your product, researches the landscape, proposes a complete design system..." + default_prompt: "Use gstack-design-consultation for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-design-review/agents/openai.yaml b/.agents/skills/gstack-design-review/agents/openai.yaml new file mode 100644 index 000000000..473554d34 --- /dev/null +++ b/.agents/skills/gstack-design-review/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-design-review" + short_description: "Designer's eye QA: finds visual inconsistency, spacing issues, hierarchy problems, AI slop patterns, and slow..." + default_prompt: "Use gstack-design-review for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-document-release/agents/openai.yaml b/.agents/skills/gstack-document-release/agents/openai.yaml new file mode 100644 index 000000000..453bf5bd1 --- /dev/null +++ b/.agents/skills/gstack-document-release/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-document-release" + short_description: "Post-ship documentation update. Reads all project docs, cross-references the diff, updates..." + default_prompt: "Use gstack-document-release for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-freeze/agents/openai.yaml b/.agents/skills/gstack-freeze/agents/openai.yaml new file mode 100644 index 000000000..0b643f68a --- /dev/null +++ b/.agents/skills/gstack-freeze/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-freeze" + short_description: "Restrict file edits to a specific directory for the session. Blocks Edit and Write outside the allowed path. Use..." + default_prompt: "Use gstack-freeze for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-guard/agents/openai.yaml b/.agents/skills/gstack-guard/agents/openai.yaml new file mode 100644 index 000000000..c7fe7902e --- /dev/null +++ b/.agents/skills/gstack-guard/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-guard" + short_description: "Full safety mode: destructive command warnings + directory-scoped edits. Combines /careful (warns before rm -rf,..." + default_prompt: "Use gstack-guard for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-investigate/agents/openai.yaml b/.agents/skills/gstack-investigate/agents/openai.yaml new file mode 100644 index 000000000..3c778414f --- /dev/null +++ b/.agents/skills/gstack-investigate/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-investigate" + short_description: "Systematic debugging with root cause investigation. Four phases: investigate, analyze, hypothesize, implement. Iron..." + default_prompt: "Use gstack-investigate for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-land-and-deploy/agents/openai.yaml b/.agents/skills/gstack-land-and-deploy/agents/openai.yaml new file mode 100644 index 000000000..73a9d7069 --- /dev/null +++ b/.agents/skills/gstack-land-and-deploy/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-land-and-deploy" + short_description: "Land and deploy workflow. Merges the PR, waits for CI and deploy, verifies production health via canary checks...." + default_prompt: "Use gstack-land-and-deploy for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-office-hours/agents/openai.yaml b/.agents/skills/gstack-office-hours/agents/openai.yaml new file mode 100644 index 000000000..51ac282dd --- /dev/null +++ b/.agents/skills/gstack-office-hours/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-office-hours" + short_description: "YC Office Hours — two modes. Startup mode: six forcing questions that expose demand reality, status quo, desperate..." + default_prompt: "Use gstack-office-hours for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-plan-ceo-review/agents/openai.yaml b/.agents/skills/gstack-plan-ceo-review/agents/openai.yaml new file mode 100644 index 000000000..6927e353f --- /dev/null +++ b/.agents/skills/gstack-plan-ceo-review/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-plan-ceo-review" + short_description: "CEO/founder-mode plan review. Rethink the problem, find the 10-star product, challenge premises, expand scope when..." + default_prompt: "Use gstack-plan-ceo-review for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-plan-design-review/agents/openai.yaml b/.agents/skills/gstack-plan-design-review/agents/openai.yaml new file mode 100644 index 000000000..d39482125 --- /dev/null +++ b/.agents/skills/gstack-plan-design-review/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-plan-design-review" + short_description: "Designer's eye plan review — interactive, like CEO and Eng review. Rates each design dimension 0-10, explains what..." + default_prompt: "Use gstack-plan-design-review for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-plan-eng-review/agents/openai.yaml b/.agents/skills/gstack-plan-eng-review/agents/openai.yaml new file mode 100644 index 000000000..96eefa75a --- /dev/null +++ b/.agents/skills/gstack-plan-eng-review/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-plan-eng-review" + short_description: "Eng manager-mode plan review. Lock in the execution plan — architecture, data flow, diagrams, edge cases, test..." + default_prompt: "Use gstack-plan-eng-review for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-qa-only/agents/openai.yaml b/.agents/skills/gstack-qa-only/agents/openai.yaml new file mode 100644 index 000000000..afbd1ee34 --- /dev/null +++ b/.agents/skills/gstack-qa-only/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-qa-only" + short_description: "Report-only QA testing. Systematically tests a web application and produces a structured report with health score,..." + default_prompt: "Use gstack-qa-only for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-qa/agents/openai.yaml b/.agents/skills/gstack-qa/agents/openai.yaml new file mode 100644 index 000000000..6d940241d --- /dev/null +++ b/.agents/skills/gstack-qa/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-qa" + short_description: "Systematically QA test a web application and fix bugs found. Runs QA testing, then iteratively fixes bugs in source..." + default_prompt: "Use gstack-qa for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-retro/agents/openai.yaml b/.agents/skills/gstack-retro/agents/openai.yaml new file mode 100644 index 000000000..dbf45f2d9 --- /dev/null +++ b/.agents/skills/gstack-retro/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-retro" + short_description: "Weekly engineering retrospective. Analyzes commit history, work patterns, and code quality metrics with persistent..." + default_prompt: "Use gstack-retro for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-review/agents/openai.yaml b/.agents/skills/gstack-review/agents/openai.yaml new file mode 100644 index 000000000..ba44751c5 --- /dev/null +++ b/.agents/skills/gstack-review/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-review" + short_description: "Pre-landing PR review. Analyzes diff against the base branch for SQL safety, LLM trust boundary violations,..." + default_prompt: "Use gstack-review for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-setup-browser-cookies/agents/openai.yaml b/.agents/skills/gstack-setup-browser-cookies/agents/openai.yaml new file mode 100644 index 000000000..5cab51862 --- /dev/null +++ b/.agents/skills/gstack-setup-browser-cookies/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-setup-browser-cookies" + short_description: "Import cookies from your real browser (Comet, Chrome, Arc, Brave, Edge) into the headless browse session. Opens an..." + default_prompt: "Use gstack-setup-browser-cookies for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-setup-deploy/agents/openai.yaml b/.agents/skills/gstack-setup-deploy/agents/openai.yaml new file mode 100644 index 000000000..b666712ef --- /dev/null +++ b/.agents/skills/gstack-setup-deploy/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-setup-deploy" + short_description: "Configure deployment settings for /land-and-deploy. Detects your deploy platform (Fly.io, Render, Vercel, Netlify,..." + default_prompt: "Use gstack-setup-deploy for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-ship/agents/openai.yaml b/.agents/skills/gstack-ship/agents/openai.yaml new file mode 100644 index 000000000..537ab1558 --- /dev/null +++ b/.agents/skills/gstack-ship/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-ship" + short_description: "Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, update CHANGELOG, commit, push,..." + default_prompt: "Use gstack-ship for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-unfreeze/agents/openai.yaml b/.agents/skills/gstack-unfreeze/agents/openai.yaml new file mode 100644 index 000000000..93de8da67 --- /dev/null +++ b/.agents/skills/gstack-unfreeze/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-unfreeze" + short_description: "Clear the freeze boundary set by /freeze, allowing edits to all directories again. Use when you want to widen edit..." + default_prompt: "Use gstack-unfreeze for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack-upgrade/agents/openai.yaml b/.agents/skills/gstack-upgrade/agents/openai.yaml new file mode 100644 index 000000000..ca055a017 --- /dev/null +++ b/.agents/skills/gstack-upgrade/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack-upgrade" + short_description: "Upgrade gstack to the latest version. Detects global vs vendored install, runs the upgrade, and shows what's new...." + default_prompt: "Use gstack-upgrade for this task." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/gstack/agents/openai.yaml b/.agents/skills/gstack/agents/openai.yaml new file mode 100644 index 000000000..ace5b243b --- /dev/null +++ b/.agents/skills/gstack/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "gstack" + short_description: "Fast headless browser for QA testing and site dogfooding. Navigate any URL, interact with elements, verify page..." + default_prompt: "Use gstack for this task." +policy: + allow_implicit_invocation: true diff --git a/agents/openai.yaml b/agents/openai.yaml new file mode 100644 index 000000000..1bb2fd7cc --- /dev/null +++ b/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "gstack" + short_description: "Bundle of gstack Codex skills" + default_prompt: "Use $gstack to locate the bundled gstack skills." diff --git a/scripts/gen-skill-docs.ts b/scripts/gen-skill-docs.ts index 08c388f69..14ab94a96 100644 --- a/scripts/gen-skill-docs.ts +++ b/scripts/gen-skill-docs.ts @@ -20,6 +20,7 @@ const DRY_RUN = process.argv.includes('--dry-run'); // ─── Template Context ─────────────────────────────────────── type Host = 'claude' | 'codex'; +const OPENAI_SHORT_DESCRIPTION_LIMIT = 120; const HOST_ARG = process.argv.find(a => a.startsWith('--host')); const HOST: Host = (() => { @@ -2200,49 +2201,33 @@ function codexSkillName(skillDir: string): string { return `gstack-${skillDir}`; } -/** - * Transform frontmatter for Codex: keep only name + description. - * Strips allowed-tools, hooks, version, and all other fields. - * Handles multiline block scalar descriptions (YAML | syntax). - */ -function transformFrontmatter(content: string, host: Host): string { - if (host === 'claude') return content; - - // Find frontmatter boundaries +function extractNameAndDescription(content: string): { name: string; description: string } { const fmStart = content.indexOf('---\n'); - if (fmStart !== 0) return content; // frontmatter must be at the start + if (fmStart !== 0) return { name: '', description: '' }; const fmEnd = content.indexOf('\n---', fmStart + 4); - if (fmEnd === -1) return content; + if (fmEnd === -1) return { name: '', description: '' }; const frontmatter = content.slice(fmStart + 4, fmEnd); - const body = content.slice(fmEnd + 4); // includes the leading \n after --- - - // Parse name const nameMatch = frontmatter.match(/^name:\s*(.+)$/m); const name = nameMatch ? nameMatch[1].trim() : ''; - // Parse description — handle both simple and block scalar (|) formats let description = ''; const lines = frontmatter.split('\n'); let inDescription = false; const descLines: string[] = []; for (const line of lines) { if (line.match(/^description:\s*\|?\s*$/)) { - // Block scalar start: "description: |" or "description:" inDescription = true; continue; } if (line.match(/^description:\s*\S/)) { - // Simple inline: "description: some text" description = line.replace(/^description:\s*/, '').trim(); break; } if (inDescription) { - // Block scalar continuation — indented lines (2 spaces) or blank lines if (line === '' || line.match(/^\s/)) { descLines.push(line.replace(/^ /, '')); } else { - // End of block scalar — hit a non-indented, non-blank line break; } } @@ -2251,6 +2236,45 @@ function transformFrontmatter(content: string, host: Host): string { description = descLines.join('\n').trim(); } + return { name, description }; +} + +function condenseOpenAIShortDescription(description: string): string { + const firstParagraph = description.split(/\n\s*\n/)[0] || description; + const collapsed = firstParagraph.replace(/\s+/g, ' ').trim(); + if (collapsed.length <= OPENAI_SHORT_DESCRIPTION_LIMIT) return collapsed; + + const truncated = collapsed.slice(0, OPENAI_SHORT_DESCRIPTION_LIMIT - 3); + const lastSpace = truncated.lastIndexOf(' '); + const safe = lastSpace > 40 ? truncated.slice(0, lastSpace) : truncated; + return `${safe}...`; +} + +function generateOpenAIYaml(displayName: string, shortDescription: string): string { + return `interface: + display_name: ${JSON.stringify(displayName)} + short_description: ${JSON.stringify(shortDescription)} + default_prompt: ${JSON.stringify(`Use ${displayName} for this task.`)} +policy: + allow_implicit_invocation: true +`; +} + +/** + * Transform frontmatter for Codex: keep only name + description. + * Strips allowed-tools, hooks, version, and all other fields. + * Handles multiline block scalar descriptions (YAML | syntax). + */ +function transformFrontmatter(content: string, host: Host): string { + if (host === 'claude') return content; + + const fmStart = content.indexOf('---\n'); + if (fmStart !== 0) return content; + const fmEnd = content.indexOf('\n---', fmStart + 4); + if (fmEnd === -1) return content; + const body = content.slice(fmEnd + 4); // includes the leading \n after --- + const { name, description } = extractNameAndDescription(content); + // Re-emit Codex frontmatter (name + description only) const indentedDesc = description.split('\n').map(l => ` ${l}`).join('\n'); const codexFm = `---\nname: ${name}\ndescription: |\n${indentedDesc}\n---`; @@ -2296,6 +2320,7 @@ function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath: const tmplContent = fs.readFileSync(tmplPath, 'utf-8'); const relTmplPath = path.relative(ROOT, tmplPath); let outputPath = tmplPath.replace(/\.tmpl$/, ''); + let outputDir: string | null = null; // Determine skill directory relative to ROOT const skillDir = path.relative(ROOT, path.dirname(tmplPath)); @@ -2303,14 +2328,14 @@ function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath: // For codex host, route output to .agents/skills/{codexSkillName}/SKILL.md if (host === 'codex') { const codexName = codexSkillName(skillDir === '.' ? '' : skillDir); - const outputDir = path.join(ROOT, '.agents', 'skills', codexName); + outputDir = path.join(ROOT, '.agents', 'skills', codexName); fs.mkdirSync(outputDir, { recursive: true }); outputPath = path.join(outputDir, 'SKILL.md'); } // Extract skill name from frontmatter for TemplateContext - const nameMatch = tmplContent.match(/^name:\s*(.+)$/m); - const skillName = nameMatch ? nameMatch[1].trim() : path.basename(path.dirname(tmplPath)); + const { name: extractedName, description: extractedDescription } = extractNameAndDescription(tmplContent); + const skillName = extractedName || path.basename(path.dirname(tmplPath)); // Extract benefits-from list from frontmatter (inline YAML: benefits-from: [a, b]) const benefitsMatch = tmplContent.match(/^benefits-from:\s*\[([^\]]*)\]/m); @@ -2352,6 +2377,15 @@ function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath: content = content.replace(/\.claude\/skills\/gstack/g, ctx.paths.localSkillRoot); content = content.replace(/\.claude\/skills\/review/g, '.agents/skills/gstack/review'); content = content.replace(/\.claude\/skills/g, '.agents/skills'); + + if (outputDir) { + const codexName = codexSkillName(skillDir === '.' ? '' : skillDir); + const agentsDir = path.join(outputDir, 'agents'); + fs.mkdirSync(agentsDir, { recursive: true }); + const displayName = codexName; + const shortDescription = condenseOpenAIShortDescription(extractedDescription); + fs.writeFileSync(path.join(agentsDir, 'openai.yaml'), generateOpenAIYaml(displayName, shortDescription)); + } } // Prepend generated header (after frontmatter) diff --git a/test/gen-skill-docs.test.ts b/test/gen-skill-docs.test.ts index d1d907a01..70c74eec9 100644 --- a/test/gen-skill-docs.test.ts +++ b/test/gen-skill-docs.test.ts @@ -704,6 +704,14 @@ describe('Codex generation (--host codex)', () => { } }); + test('root gstack bundle has OpenAI metadata for Codex skill browsing', () => { + const rootMetadata = path.join(ROOT, 'agents', 'openai.yaml'); + expect(fs.existsSync(rootMetadata)).toBe(true); + const content = fs.readFileSync(rootMetadata, 'utf-8'); + expect(content).toContain('display_name: "gstack"'); + expect(content).toContain('Use $gstack to locate the bundled gstack skills.'); + }); + test('codexSkillName mapping: root is gstack, others are gstack-{dir}', () => { // Root → gstack expect(fs.existsSync(path.join(AGENTS_DIR, 'gstack', 'SKILL.md'))).toBe(true); @@ -733,6 +741,17 @@ describe('Codex generation (--host codex)', () => { } }); + test('all Codex skills have agents/openai.yaml metadata', () => { + for (const skill of CODEX_SKILLS) { + const metadata = path.join(AGENTS_DIR, skill.codexName, 'agents', 'openai.yaml'); + expect(fs.existsSync(metadata)).toBe(true); + const content = fs.readFileSync(metadata, 'utf-8'); + expect(content).toContain(`display_name: "${skill.codexName}"`); + expect(content).toContain('short_description:'); + expect(content).toContain('allow_implicit_invocation: true'); + } + }); + test('no .claude/skills/ in Codex output', () => { for (const skill of CODEX_SKILLS) { const content = fs.readFileSync(path.join(AGENTS_DIR, skill.codexName, 'SKILL.md'), 'utf-8'); diff --git a/test/helpers/codex-session-runner.ts b/test/helpers/codex-session-runner.ts index 77b45020b..ac2b9e298 100644 --- a/test/helpers/codex-session-runner.ts +++ b/test/helpers/codex-session-runner.ts @@ -98,7 +98,8 @@ export function parseCodexJSONL(lines: string[]): ParsedCodexJSONL { /** * Install a SKILL.md into a temp HOME directory for Codex to discover. - * Creates ~/.codex/skills/{skillName}/SKILL.md in the temp HOME. + * Creates ~/.codex/skills/{skillName}/SKILL.md in the temp HOME and copies + * agents/openai.yaml when present so Codex sees the same metadata as a real install. * * Returns the temp HOME path. Caller is responsible for cleanup. */ @@ -116,6 +117,13 @@ export function installSkillToTempHome( fs.copyFileSync(srcSkill, path.join(destDir, 'SKILL.md')); } + const srcOpenAIYaml = path.join(skillDir, 'agents', 'openai.yaml'); + if (fs.existsSync(srcOpenAIYaml)) { + const destAgentsDir = path.join(destDir, 'agents'); + fs.mkdirSync(destAgentsDir, { recursive: true }); + fs.copyFileSync(srcOpenAIYaml, path.join(destAgentsDir, 'openai.yaml')); + } + return home; }