mirror of https://github.com/garrytan/gstack.git
fix: preserve generated skill links
This commit is contained in:
parent
a3259400a3
commit
4b41f638a7
|
|
@ -16,7 +16,6 @@ Conventions:
|
|||
- [/browse](browse/SKILL.md): Fast headless browser for QA testing and site dogfooding.
|
||||
- [/canary](canary/SKILL.md): Post-deploy canary monitoring.
|
||||
- [/careful](careful/SKILL.md): Safety guardrails for destructive commands.
|
||||
- [/claude](claude/SKILL.md): Claude Code CLI wrapper for non-Claude hosts - three modes.
|
||||
- [/codex](codex/SKILL.md): OpenAI Codex CLI wrapper — three modes.
|
||||
- [/context-restore](context-restore/SKILL.md): Restore working context saved earlier by /context-save.
|
||||
- [/context-save](context-save/SKILL.md): Save working context.
|
||||
|
|
@ -30,7 +29,7 @@ Conventions:
|
|||
- [/document-generate](document-generate/SKILL.md): Generate missing documentation from scratch for a feature, module, or entire project.
|
||||
- [/document-release](document-release/SKILL.md): Post-ship documentation update.
|
||||
- [/freeze](freeze/SKILL.md): Restrict file edits to a specific directory for the session.
|
||||
- [/gstack](gstack/SKILL.md): Router for the gstack skill suite.
|
||||
- [/gstack](SKILL.md): Router for the gstack skill suite.
|
||||
- [/gstack-upgrade](gstack-upgrade/SKILL.md): Upgrade gstack to the latest version.
|
||||
- [/guard](guard/SKILL.md): Full safety mode: destructive command warnings + directory-scoped edits.
|
||||
- [/health](health/SKILL.md): Code quality dashboard.
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ const OUTPUT = path.join(ROOT, 'gstack', 'llms.txt');
|
|||
interface SkillEntry {
|
||||
name: string;
|
||||
description: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -113,6 +114,11 @@ function oneLine(text: string): string {
|
|||
return first.replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function skillDirForOutput(output: string): string {
|
||||
const dir = path.dirname(output);
|
||||
return dir === '.' ? '.' : dir;
|
||||
}
|
||||
|
||||
interface GenerateOptions {
|
||||
/** Override repo root (for tests). */
|
||||
root?: string;
|
||||
|
|
@ -135,6 +141,9 @@ export async function generateLlmsTxt(opts: GenerateOptions = {}): Promise<Gener
|
|||
const templates = discoverTemplates(root);
|
||||
const skills: SkillEntry[] = [];
|
||||
for (const t of templates) {
|
||||
if (skillDirForOutput(t.output) === 'claude') {
|
||||
continue;
|
||||
}
|
||||
const filePath = path.join(root, t.tmpl);
|
||||
const entry = parseSkillFrontmatter(filePath);
|
||||
if (!entry) {
|
||||
|
|
@ -144,7 +153,7 @@ export async function generateLlmsTxt(opts: GenerateOptions = {}): Promise<Gener
|
|||
}
|
||||
continue;
|
||||
}
|
||||
skills.push(entry);
|
||||
skills.push({ ...entry, href: t.output });
|
||||
}
|
||||
skills.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
|
|
@ -167,7 +176,7 @@ export async function generateLlmsTxt(opts: GenerateOptions = {}): Promise<Gener
|
|||
lines.push('');
|
||||
for (const skill of skills) {
|
||||
const summary = oneLine(skill.description);
|
||||
lines.push(`- [/${skill.name}](${skill.name}/SKILL.md): ${summary}`);
|
||||
lines.push(`- [/${skill.name}](${skill.href}): ${summary}`);
|
||||
}
|
||||
lines.push('');
|
||||
|
||||
|
|
|
|||
|
|
@ -25,17 +25,29 @@ describe('gen-llms-txt — shape', () => {
|
|||
expect(generated.content).toContain('auto-generated');
|
||||
});
|
||||
|
||||
test('every skill .tmpl in the repo appears in the index', () => {
|
||||
test('every generated Claude skill appears in the index', () => {
|
||||
const templates = discoverTemplates(ROOT);
|
||||
const claudeGeneratedTemplates = templates.filter((t) => path.dirname(t.output) !== 'claude');
|
||||
// Filter to those that successfully parsed (have name + description).
|
||||
expect(generated.skills.length).toBeGreaterThan(0);
|
||||
expect(generated.skills.length).toBeLessThanOrEqual(templates.length);
|
||||
expect(generated.skills.length).toBeLessThanOrEqual(claudeGeneratedTemplates.length);
|
||||
expect(generated.content).not.toContain('[/claude]');
|
||||
|
||||
for (const skill of generated.skills) {
|
||||
expect(generated.content).toMatch(new RegExp(`/${skill.name}\\b`));
|
||||
}
|
||||
});
|
||||
|
||||
test('every skill link points at an existing generated file', () => {
|
||||
const skillsSection = generated.content.split('## Skills')[1].split('## Browse Commands')[0];
|
||||
const links = [...skillsSection.matchAll(/- \[\/[^\]]+\]\(([^)]+)\):/g)].map((m) => m[1]);
|
||||
expect(links.length).toBe(generated.skills.length);
|
||||
|
||||
for (const href of links) {
|
||||
expect(fs.existsSync(path.join(ROOT, href)), href).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('every browse command in COMMAND_DESCRIPTIONS appears in the index', () => {
|
||||
expect(generated.browseCommands.length).toBeGreaterThan(0);
|
||||
for (const cmd of generated.browseCommands) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue