C6: gstack-relink discovers skills structurally, so /browse survives a skill_prefix toggle

relink skipped a hardcoded list of support-directory names that included
browse, a real skill, so toggling skill_prefix removed /browse. A top-level
directory is now a skill exactly when it carries a SKILL.md.

Co-authored-by: Jonathan Zhang <topcoder1@Mac-Studio.local>
This commit is contained in:
garrytanandJonathan Zhang committed 2026-10-03 21:04:49 +00:00
1 parent 82a79be3f2
commit 68cabd06a6
2 files changed
+29 -3

No files matched your search

+3 -3
View File
@@ -330,7 +330,7 @@ _link_root_skill_alias() {
_link_root_skill_alias
# Discover skills (directories with SKILL.md, excluding meta dirs)
# Discover skills: every top-level directory with a SKILL.md
SKILL_COUNT=0
for skill_dir in "$INSTALL_DIR"/*/; do
[ -d "$skill_dir" ] || continue
@@ -340,8 +340,8 @@ for skill_dir in "$INSTALL_DIR"/*/; do
# rewritten-copy alias for those.
[ -L "${skill_dir%/}" ] && continue
skill=$(basename "$skill_dir")
# Skip non-skill directories
case "$skill" in bin|browse|design|docs|extension|lib|node_modules|scripts|test|.git|.github) continue ;; esac
# A directory is a skill iff it carries a SKILL.md (#2263): a name list
# dropped the real browse skill. Support dirs have no SKILL.md.
[ -f "$skill_dir/SKILL.md" ] || continue
case "$DISABLED" in
*" ${skill#gstack-} "*)
+26
View File
@@ -526,6 +526,32 @@ describe('gstack-relink (#578)', () => {
expect(fs.existsSync(path.join(skillsDir, 'gstack-qa'))).toBe(true);
expect(fs.existsSync(path.join(skillsDir, 'gstack-ship'))).toBe(true);
});
// C6 (#2263): discovery is structural (has a SKILL.md), never name-based. A
// hardcoded meta-dir list dropped the real `browse` skill, so /browse
// disappeared after toggling skill_prefix.
test('materializes every dir carrying a SKILL.md, even names once meta-excluded (browse)', () => {
const formerlyExcluded = ['bin', 'browse', 'design', 'docs', 'extension', 'lib', 'node_modules', 'scripts', 'test'];
setupMockInstall(['qa', ...formerlyExcluded]);
const env = { GSTACK_INSTALL_DIR: installDir, GSTACK_SKILLS_DIR: skillsDir };
run(`${path.join(installDir, 'bin', 'gstack-config')} set skill_prefix false`, env);
run(`${path.join(installDir, 'bin', 'gstack-relink')}`, env);
const missing = ['qa', ...formerlyExcluded].filter(s => !fs.existsSync(path.join(skillsDir, s, 'SKILL.md')));
expect(missing).toEqual([]);
});
test('skips top-level dirs that have no SKILL.md', () => {
setupMockInstall(['qa']);
for (const meta of ['node_modules', 'lib', 'scripts']) {
fs.mkdirSync(path.join(installDir, meta), { recursive: true });
fs.writeFileSync(path.join(installDir, meta, 'placeholder.txt'), 'not a skill');
}
const env = { GSTACK_INSTALL_DIR: installDir, GSTACK_SKILLS_DIR: skillsDir };
run(`${path.join(installDir, 'bin', 'gstack-config')} set skill_prefix false`, env);
run(`${path.join(installDir, 'bin', 'gstack-relink')}`, env);
expect(fs.existsSync(path.join(skillsDir, 'qa', 'SKILL.md'))).toBe(true);
expect(['bin', 'node_modules', 'lib', 'scripts'].filter(m => fs.existsSync(path.join(skillsDir, m)))).toEqual([]);
});
});
describe('upgrade migrations', () => {