fix(setup): make gstack skills discoverable and usable in Factory Droid#660
Open
morluto wants to merge 5 commits intogarrytan:mainfrom
Open
fix(setup): make gstack skills discoverable and usable in Factory Droid#660morluto wants to merge 5 commits intogarrytan:mainfrom
morluto wants to merge 5 commits intogarrytan:mainfrom
Conversation
…roid
## Problem
Droid scans ~/.factory/skills/ for skill directories. It follows *relative*
symlinks pointing to ../../.agents/skills/{skill}, but ignores absolute paths.
When setup ran with --host factory, it created absolute symlinks:
ln -snf /Users/will/gstack/.factory/skills/gstack-qa ~/.factory/skills/gstack-qa
Droid ignored these entirely — no skills appeared in the '/' menu.
## Root Cause
Droid's filesystem scanner only resolves relative symlinks. Absolute symlinks
pointing to paths outside ~/.agents/skills/ are silently skipped.
## Reproduction
1. Clone gstack: git clone https://github.com/garrytan/gstack.git ~/gstack
2. Run setup: cd ~/gstack && ./setup --host factory
3. Check symlinks: ls -la ~/.factory/skills/ | grep gstack
4. Observe: gstack symlinks are absolute paths — WRONG
5. In Droid, type '/' — no gstack skills visible
## Fix
Change symlink creation from absolute to relative paths:
ln -snf "../../.agents/skills/$skill_name" "$target"
Also removes the 'gstack' skip that prevented linking the root skill.
## Validation
After fix:
- ls ~/.factory/skills/gstack-qa points to '../../.agents/skills/gstack-qa'
- Droid '/' menu shows gstack skills after restart
- Verified all working skills (debug, fix, test) use same relative pattern
## Problem
Skills were installed to ~/gstack/.factory/skills/ (a project subdirectory).
Droid never scans this location — it only scans ~/.factory/skills/ and reads
skill files from ~/.agents/skills/.
Even with correct relative symlinks in ~/.factory/skills/, Droid could not load
skills because the actual skill files didn't exist in ~/.agents/skills/.
## Root Cause
Droid's skill loading requires skill files to exist in ~/.agents/skills/.
Symlinks in ~/.factory/skills/ point to skill locations, but the files must
actually be present at the target.
## Reproduction
1. Run: cd ~/gstack && ./setup --host factory
2. Check: ls ~/.agents/skills/ | grep gstack
3. Observe: no gstack directories — SKILL.md files are in ~/gstack/.factory/skills/
4. Droid reports skills as unavailable
## Fix
Copy skills as real directories into ~/.agents/skills/:
mkdir -p "$HOME/.agents/skills"
for skill_dir in "$factory_dir"/gstack*/; do
cp -r "$skill_dir" "$HOME/.agents/skills/"
done
This ensures Droid can read the actual SKILL.md files.
## Validation
After fix:
- ls ~/.agents/skills/ shows all 31 gstack skill directories
- Each directory contains SKILL.md and supporting files
- Droid can read skill metadata from ~/.agents/skills/
…=github
## Problem
Droid maintains a skill registry at ~/.agents/.skill-lock.json. It ONLY loads
entries where sourceType=github. Entries with sourceType=local are silently ignored.
The old setup registered skills with sourceType=local — producing dead lockfile
entries that Droid never read.
## Root Cause
Droid's lockfile reader checks 'sourceType' to determine if a skill should be
loaded. No working Droid skill uses sourceType=local — it's a no-op.
## Reproduction
1. Run: cd ~/gstack && ./setup --host factory
2. Check lockfile: python3 -c "import json; l=json.load(open('/Users/will/.agents/.skill-lock.json')); print({k:v['sourceType'] for k,v in l['skills'].items() if 'gstack' in k})"
3. Observe: all gstack entries have sourceType=local — NOT LOADED
4. Compare: working skills (debug, fix, test) have sourceType=github
## Fix
Register skills with sourceType=github in ~/.agents/.skill-lock.json:
lock['skills'][entry] = {
'source': 'gstack/' + entry,
'sourceType': 'github',
'sourceUrl': 'file://' + os.path.join(agents_skills, entry),
...
}
This matches the format of all working Droid skills.
## Validation
After fix:
- python3 -c "import json; l=json.load(open('/Users/will/.agents/.skill-lock.json')); print(sum(1 for v in l['skills'].values() if v.get('sourceType')=='github'))"
- Shows count of github-sourced skills includes gstack
- Droid '/' menu shows gstack skills (e.g. /qa)
- Skill name comes from 'name:' field in SKILL.md, not directory name
## Problem Factory install wrote Droid-specific skill files directly into ~/.agents/skills/gstack-*. That path is also the shared SKILL.md convention for non-Factory hosts, so installing for Droid could clobber an existing Gemini/Cursor-style gstack install with Factory-only preambles and frontmatter. The same path also skipped the registry write on clean machines. If ~/.agents/.skill-lock.json did not exist yet, setup warned, skipped registration, and still printed a success message. That meant the branch fixed upgrades better than first-time installs. ## Root Cause link_factory_skill_dirs() copied each Factory-generated skill into ~/.agents/skills using the public gstack-* names, then pointed ~/.factory/skills symlinks at those shared names. This assumed the shared ~/.agents/skills namespace was safe to overwrite, but the generated Factory skill files are not equivalent to the non-Factory variants. The lockfile path also assumed Droid had already created ~/.agents/.skill-lock.json. On a clean profile there was no bootstrap step, so the registration code never ran. ## Fix Copy Factory-generated skills into isolated ~/.agents/skills/gstack-factory* directories instead of overwriting the shared gstack-* names. Keep the public ~/.factory/skills/gstack-* symlink names stable, but point them at the isolated Factory copies. Bootstrap ~/.agents/.skill-lock.json on first install with the minimal top-level shape Droid expects, then register only the Factory-owned public skill names while pointing sourceUrl at the isolated copy directories. Preserve existing installedAt values when rewriting those entries. Also add setup validation tests that lock in the isolated copy naming, clean lockfile bootstrap, and the lockfile mapping between public skill names and isolated Factory copy paths. ## Validation - bun test test/gen-skill-docs.test.ts - bash -n setup
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Factory Droid support is currently broken for gstack installs via:
The generated skills exist on disk, but Droid cannot reliably discover or load them. In practice this means gstack skills may not appear in the
/menu, and the install layout is not compatible with Droid's expected discovery and registry model.Reproduction
/— no gstack skills appear (debug, fix, test, qa, etc. are missing)Root Cause: Three Bugs in
link_factory_skill_dirs()Bug 1: Wrong target for installed skill contents
The original install did create entries in
~/.factory/skills/, but they pointed to the wrong place for discovery. gstack generated Factory skills in~/gstack/.factory/skills/and then linked~/.factory/skills/entries back into that project directory, while Droid expects the canonical skill contents to live under~/.agents/skills/.Bug 2: Absolute symlinks
gstack created absolute symlinks in
~/.factory/skills/:ln -snf /Users/will/gstack/.factory/skills/gstack-qa ~/.factory/skills/gstack-qaDroid follows only relative symlinks (
../../.agents/skills/{skill}). Absolute paths are ignored.Bug 3: Wrong
sourceTypein lockfilegstack registered entries with
sourceType: "local":{ "sourceType": "local", "sourceUrl": "file:///Users/will/gstack/.factory/skills/gstack-qa" }Droid's lockfile reader ignores
sourceType: "local"entirely. Zero of the 57 working skills use this value — all usesourceType: "github".How Droid Discovers Skills (Two-Tier System)
A skill appears in
/when both conditions are met:~/.factory/skills/pointing to the isolated Factory copy in~/.agents/skills/gstack-factory*gstack-qa) withsourceType: "github"in~/.agents/.skill-lock.jsonThe Fix (4 commits)
Commit 1: Use relative symlinks in
~/.factory/skills/Changed from absolute paths to the pattern all working skills use:
This commit also removes the old skip for the root
gstackskill, so that directory can now be linked like the rest of the skill set.Commit 2: Copy skills to
~/.agents/skills/Droid reads skill files from
~/.agents/skills/. Symlinks pointing into~/gstack/.factory/skills/do not work — must be real directories.This means Factory now reads a copied snapshot from
~/.agents/skills/, not directly from the repo checkout. If you edit a skill in~/gstack, rerun./setup --host factoryto refresh Droid's copy.Commit 3: Register with
sourceType: "github"Updated the Python lockfile registration script to use the only
sourceTypeDroid actually loads:{ "source": "gstack/gstack-qa", "sourceType": "github", "sourceUrl": "file:///Users/will/.agents/skills/gstack-qa" }Commit 4: Isolate Factory copies and bootstrap the lockfile
The first three fixes made Droid discovery work, but they still wrote Factory-generated skill files into the shared
~/.agents/skills/gstack-*namespace. That risked clobbering non-Factory installs that also use~/.agents/skills/.The final fix isolates Factory-owned copies under
gstack-factory*names while keeping the public Droid-facing names unchanged:It also bootstraps
~/.agents/.skill-lock.jsonon first install instead of skipping registration on clean machines:{ "version": 3, "dismissed": {}, "lastSelectedAgents": [], "skills": {} }The lockfile still registers the public skill name (
gstack-qa) so Droid shows the expected/qacommand, but thesourceUrlnow points at the isolated Factory copy:{ "source": "gstack/gstack-qa", "sourceType": "github", "sourceUrl": "file:///Users/will/.agents/skills/gstack-factory-qa" }Validation
After applying the fix:
Closes #661.