Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .agents/skills/gstack-autoplan/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-benchmark/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-browse/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-canary/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-careful/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-cso/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-design-consultation/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-design-review/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-document-release/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-freeze/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-guard/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-investigate/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-land-and-deploy/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-office-hours/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-plan-ceo-review/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-plan-design-review/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-plan-eng-review/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-qa-only/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-qa/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-retro/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-review/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-setup-deploy/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-ship/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-unfreeze/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack-upgrade/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions .agents/skills/gstack/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -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."
78 changes: 56 additions & 22 deletions scripts/gen-skill-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (() => {
Expand Down Expand Up @@ -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;
}
}
Expand All @@ -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---`;
Expand Down Expand Up @@ -2296,21 +2320,22 @@ 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));

// 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);
Expand Down Expand Up @@ -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)
Expand Down
19 changes: 19 additions & 0 deletions test/gen-skill-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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');
Expand Down
10 changes: 9 additions & 1 deletion test/helpers/codex-session-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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;
}

Expand Down