mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-20 03:12:28 +08:00
feat: use frontmatter name: for skill symlinks and Codex paths
Patch all 3 name-derivation paths to read name: from SKILL.md frontmatter instead of relying solely on directory basenames. This enables directory names that differ from invocation names (e.g., run-tests/ directory with name: test). - setup: link_claude_skill_dirs reads name: via grep, falls back to basename - gen-skill-docs.ts: codexSkillName uses frontmatter name for Codex output paths - gen-skill-docs.ts: moved frontmatter extraction before Codex path logic Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -76,11 +76,13 @@ const OPENAI_LITMUS_CHECKS = [
|
|||||||
|
|
||||||
// ─── Codex Helpers ───────────────────────────────────────────
|
// ─── Codex Helpers ───────────────────────────────────────────
|
||||||
|
|
||||||
function codexSkillName(skillDir: string): string {
|
function codexSkillName(skillDir: string, frontmatterName?: string): string {
|
||||||
if (skillDir === '.' || skillDir === '') return 'gstack';
|
// Use frontmatter name: if it differs from directory name (e.g., run-tests/ with name: test)
|
||||||
|
const baseName = frontmatterName && frontmatterName !== skillDir ? frontmatterName : skillDir;
|
||||||
|
if (baseName === '.' || baseName === '') return 'gstack';
|
||||||
// Don't double-prefix: gstack-upgrade → gstack-upgrade (not gstack-gstack-upgrade)
|
// Don't double-prefix: gstack-upgrade → gstack-upgrade (not gstack-gstack-upgrade)
|
||||||
if (skillDir.startsWith('gstack-')) return skillDir;
|
if (baseName.startsWith('gstack-')) return baseName;
|
||||||
return `gstack-${skillDir}`;
|
return `gstack-${baseName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractNameAndDescription(content: string): { name: string; description: string } {
|
function extractNameAndDescription(content: string): { name: string; description: string } {
|
||||||
@@ -217,12 +219,18 @@ function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath:
|
|||||||
// Determine skill directory relative to ROOT
|
// Determine skill directory relative to ROOT
|
||||||
const skillDir = path.relative(ROOT, path.dirname(tmplPath));
|
const skillDir = path.relative(ROOT, path.dirname(tmplPath));
|
||||||
|
|
||||||
|
// Extract skill name from frontmatter early — needed for both TemplateContext and Codex output paths.
|
||||||
|
// When frontmatter name: differs from directory name (e.g., run-tests/ with name: test),
|
||||||
|
// the frontmatter name is used for Codex skill naming and setup script symlinks.
|
||||||
|
const { name: extractedName, description: extractedDescription } = extractNameAndDescription(tmplContent);
|
||||||
|
const skillName = extractedName || path.basename(path.dirname(tmplPath));
|
||||||
|
|
||||||
let outputDir: string | null = null;
|
let outputDir: string | null = null;
|
||||||
|
|
||||||
// For codex host, route output to .agents/skills/{codexSkillName}/SKILL.md
|
// For codex host, route output to .agents/skills/{codexSkillName}/SKILL.md
|
||||||
let symlinkLoop = false;
|
let symlinkLoop = false;
|
||||||
if (host === 'codex') {
|
if (host === 'codex') {
|
||||||
const codexName = codexSkillName(skillDir === '.' ? '' : skillDir);
|
const codexName = codexSkillName(skillDir === '.' ? '' : skillDir, extractedName || undefined);
|
||||||
outputDir = path.join(ROOT, '.agents', 'skills', codexName);
|
outputDir = path.join(ROOT, '.agents', 'skills', codexName);
|
||||||
fs.mkdirSync(outputDir, { recursive: true });
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
outputPath = path.join(outputDir, 'SKILL.md');
|
outputPath = path.join(outputDir, 'SKILL.md');
|
||||||
@@ -243,10 +251,6 @@ function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract skill name from frontmatter for TemplateContext
|
|
||||||
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])
|
// Extract benefits-from list from frontmatter (inline YAML: benefits-from: [a, b])
|
||||||
const benefitsMatch = tmplContent.match(/^benefits-from:\s*\[([^\]]*)\]/m);
|
const benefitsMatch = tmplContent.match(/^benefits-from:\s*\[([^\]]*)\]/m);
|
||||||
const benefitsFrom = benefitsMatch
|
const benefitsFrom = benefitsMatch
|
||||||
@@ -296,7 +300,7 @@ function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath:
|
|||||||
content = content.replace(/\.claude\/skills/g, '.agents/skills');
|
content = content.replace(/\.claude\/skills/g, '.agents/skills');
|
||||||
|
|
||||||
if (outputDir && !symlinkLoop) {
|
if (outputDir && !symlinkLoop) {
|
||||||
const codexName = codexSkillName(skillDir === '.' ? '' : skillDir);
|
const codexName = codexSkillName(skillDir === '.' ? '' : skillDir, extractedName || undefined);
|
||||||
const agentsDir = path.join(outputDir, 'agents');
|
const agentsDir = path.join(outputDir, 'agents');
|
||||||
fs.mkdirSync(agentsDir, { recursive: true });
|
fs.mkdirSync(agentsDir, { recursive: true });
|
||||||
const displayName = codexName;
|
const displayName = codexName;
|
||||||
|
|||||||
9
setup
9
setup
@@ -250,9 +250,12 @@ link_claude_skill_dirs() {
|
|||||||
local linked=()
|
local linked=()
|
||||||
for skill_dir in "$gstack_dir"/*/; do
|
for skill_dir in "$gstack_dir"/*/; do
|
||||||
if [ -f "$skill_dir/SKILL.md" ]; then
|
if [ -f "$skill_dir/SKILL.md" ]; then
|
||||||
skill_name="$(basename "$skill_dir")"
|
dir_name="$(basename "$skill_dir")"
|
||||||
# Skip node_modules
|
# Skip node_modules
|
||||||
[ "$skill_name" = "node_modules" ] && continue
|
[ "$dir_name" = "node_modules" ] && continue
|
||||||
|
# Use frontmatter name: if present (e.g., run-tests/ with name: test → symlink as "test")
|
||||||
|
skill_name=$(grep -m1 '^name:' "$skill_dir/SKILL.md" 2>/dev/null | sed 's/^name:[[:space:]]*//' | tr -d '[:space:]')
|
||||||
|
[ -z "$skill_name" ] && skill_name="$dir_name"
|
||||||
# Apply gstack- prefix unless --no-prefix or already prefixed
|
# Apply gstack- prefix unless --no-prefix or already prefixed
|
||||||
if [ "$SKILL_PREFIX" -eq 1 ]; then
|
if [ "$SKILL_PREFIX" -eq 1 ]; then
|
||||||
case "$skill_name" in
|
case "$skill_name" in
|
||||||
@@ -265,7 +268,7 @@ link_claude_skill_dirs() {
|
|||||||
target="$skills_dir/$link_name"
|
target="$skills_dir/$link_name"
|
||||||
# Create or update symlink; skip if a real file/directory exists
|
# Create or update symlink; skip if a real file/directory exists
|
||||||
if [ -L "$target" ] || [ ! -e "$target" ]; then
|
if [ -L "$target" ] || [ ! -e "$target" ]; then
|
||||||
ln -snf "gstack/$skill_name" "$target"
|
ln -snf "gstack/$dir_name" "$target"
|
||||||
linked+=("$link_name")
|
linked+=("$link_name")
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1639,11 +1639,12 @@ describe('setup script validation', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('link_claude_skill_dirs creates relative symlinks', () => {
|
test('link_claude_skill_dirs creates relative symlinks', () => {
|
||||||
// Claude links should be relative: ln -snf "gstack/skill_name"
|
// Claude links should be relative: ln -snf "gstack/$dir_name"
|
||||||
|
// Uses dir_name (not skill_name) because symlink target must point to the physical directory
|
||||||
const fnStart = setupContent.indexOf('link_claude_skill_dirs()');
|
const fnStart = setupContent.indexOf('link_claude_skill_dirs()');
|
||||||
const fnEnd = setupContent.indexOf('}', setupContent.indexOf('linked[@]}', fnStart));
|
const fnEnd = setupContent.indexOf('}', setupContent.indexOf('linked[@]}', fnStart));
|
||||||
const fnBody = setupContent.slice(fnStart, fnEnd);
|
const fnBody = setupContent.slice(fnStart, fnEnd);
|
||||||
expect(fnBody).toContain('ln -snf "gstack/$skill_name"');
|
expect(fnBody).toContain('ln -snf "gstack/$dir_name"');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('setup supports --host auto|claude|codex|kiro', () => {
|
test('setup supports --host auto|claude|codex|kiro', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user