Skip to content

fix(relink): discover skills structurally so browse is materialized#2263

Open
topcoder1 wants to merge 1 commit into
garrytan:mainfrom
topcoder1:fix/relink-structural-skill-discovery
Open

fix(relink): discover skills structurally so browse is materialized#2263
topcoder1 wants to merge 1 commit into
garrytan:mainfrom
topcoder1:fix/relink-structural-skill-discovery

Conversation

@topcoder1

Copy link
Copy Markdown

Problem

gstack-relink gates skill discovery on a hardcoded meta-directory name list:

case "$skill" in bin|browse|design|docs|extension|lib|node_modules|scripts|test|.git|.github) continue ;; esac

browse is in that list — but browse is a real, live skill: browse/SKILL.md exists with name: browse, version: 1.1.0, "Fast headless browser for QA testing and site dogfooding". Two shipped skills depend on it by name: benchmark ("Performance regression detection using the browse daemon") and setup-browser-cookies ("Import cookies … into the headless browse session").

The result: gstack-relink never materializes skills/browse, so the browse skill is unavailable. It only appeared to work historically because a downstream consumer tracked a skills/browse symlink that papered over the exclusion. Once that symlink is removed, browse is permanently missing on any fresh install (or after any skills/ rebuild) and cannot self-heal via relink.

Fix

browse is the only one of the nine non-dot excluded names that actually carries a SKILL.md. The eight others (bin, design, docs, extension, lib, node_modules, scripts, test) have none — so the structural guard already on the next line handles them for free:

[ -f "$skill_dir/SKILL.md" ] || continue

This PR drops the redundant name list and makes that structural test the sole gate. A directory is a skill iff it carries a SKILL.md.

This is not a new convention — it aligns relink with the two sibling discovery paths that are already structural:

  • scripts/discover-skills.ts (canonical shared discovery, used by CI + doc-gen): its skip set is only { node_modules, .git, dist } — it already treats browse as a skill.
  • bin/gstack-patch-names: iterates the same dirs using only [ -f "$skill_dir/SKILL.md" ], no name list.

It also future-proofs design/docs/test should any ever become real skills.

Safety

relink's glob is top-level */ only and stats just $dir/SKILL.md. The three names in discover-skills' skip set are already handled: .git is excluded by the glob (dotdirs never match */), and neither node_modules/SKILL.md nor a top-level dist/SKILL.md exists (dist only appears nested, e.g. browse/dist). No top-level test//docs/ SKILL.md exists, so no fixture is mistaken for a skill.

Tests

test/relink.test.ts gains three regression tests (suite: 27 pass, 0 fail — was 25 pass + 2 fail before the fix):

  • real payload: enumerates every top-level */SKILL.md in the repo and asserts each materializes — fails on exactly browse without the fix.
  • future-proofing: mocks an install with dirs named after every former-exclusion entry, each carrying a SKILL.md, and asserts all materialize.
  • companion invariant: dirs without a SKILL.md are still skipped.

Before: Relinked 53 skills. After: Relinked 54 skills, with browse/SKILL.md present.

`gstack-relink` gated skill discovery on a hardcoded meta-directory name
list (`bin|browse|design|docs|extension|lib|node_modules|scripts|test|.git|.github`).
`browse` is a real, live skill — `browse/SKILL.md` (name: browse, v1.1.0),
depended on by `benchmark` and `setup-browser-cookies` — so the name
collision meant relink never materialized it. It only appeared to work
historically because a tracked `skills/browse` symlink in a downstream
consumer papered over the gap; once that was removed, `browse` went
permanently missing on fresh installs and could not self-heal via relink.

`browse` was the only one of the nine non-dot excluded names that actually
carries a SKILL.md; the structural guard `[ -f "$skill_dir/SKILL.md" ]` on
the next line already handles every genuinely SKILL-less support dir. Drop
the redundant name list and let that structural test be the sole gate. This
matches gstack's canonical discovery (`scripts/discover-skills.ts`, whose
SKIP set is only node_modules/.git/dist) and the companion `gstack-patch-names`
(structural, no name list), and future-proofs `design`/`docs`/`test` should
any ever become real skills.

Adds regression tests: a real-payload assertion that every top-level
`*/SKILL.md` materializes (fails on exactly `browse` pre-fix), a
future-proofing test over all former-exclusion names, and a companion test
that SKILL-less dirs stay skipped.
@trunk-io

trunk-io Bot commented Jul 14, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@time-attack

time-attack commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

@topcoder1 Restore reserved-directory exclusions while keeping structural discovery for browse, so dependency and build content cannot be exposed as agent skills.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants