Skip to content

fix(setup): make gstack skills discoverable by Factory Droid#659

Closed
morluto wants to merge 3 commits intogarrytan:mainfrom
morluto:droid_setup_fixes
Closed

fix(setup): make gstack skills discoverable by Factory Droid#659
morluto wants to merge 3 commits intogarrytan:mainfrom
morluto:droid_setup_fixes

Conversation

@morluto
Copy link
Copy Markdown
Contributor

@morluto morluto commented Mar 30, 2026

Problem

Skills installed via cd ~/gstack && ./setup --host factory did not appear in Droid's / menu. The skills existed on disk but were invisible to Droid's discovery system.

Reproduction

  1. Install gstack for Factory Droid:
    cd ~/gstack && ./setup --host factory
  2. Restart Droid
  3. Type / — no gstack skills appear (debug, fix, test, qa, etc. are missing)
  4. Check the installed entries:
    ls ~/gstack/.factory/skills/    # generated skill directories exist in the repo
    ls -l ~/.factory/skills/        # gstack entries exist, but as absolute symlinks into ~/gstack
    ls ~/.agents/skills/            # empty — skills not copied here

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-qa

Droid follows only relative symlinks (../../.agents/skills/{skill}). Absolute paths are ignored.

Bug 3: Wrong sourceType in lockfile

gstack 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 use sourceType: "github".

How Droid Discovers Skills (Two-Tier System)

Droid startup
    │
    ├── Tier 1: Scans ~/.factory/skills/
    │       ├── Follows relative symlinks ──→ ~/.agents/skills/{skill}/SKILL.md
    │       └── Ignores: absolute symlinks and project-local targets
    │
    └── Tier 2: Reads ~/.agents/.skill-lock.json (if present)
            ├── sourceType: "github"  ──→ loads skill ✓
            └── sourceType: "local"  ──→ ignores skill ✗

A skill appears in / when both conditions are met:

  1. Relative symlink exists in ~/.factory/skills/ pointing to ~/.agents/skills/{skill}
  2. Lockfile entry has sourceType: "github" in ~/.agents/.skill-lock.json

The Fix (3 commits)

Commit 1: Use relative symlinks in ~/.factory/skills/

Changed from absolute paths to the pattern all working skills use:

ln -snf "../../.agents/skills/$skill_name" "$target"

This commit also removes the old skip for the root gstack skill, 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.

cp -r "$skill_dir" "$HOME/.agents/skills/$skill_name"

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 factory to refresh Droid's copy.

Commit 3: Register with sourceType: "github"

Updated the Python lockfile registration script to use the only sourceType Droid actually loads:

{
  "source": "gstack/gstack-qa",
  "sourceType": "github",
  "sourceUrl": "file:///Users/will/.agents/skills/gstack-qa"
}

Validation

After applying the fix:

# 1. Install
cd ~/gstack && ./setup --host factory

# 2. Verify filesystem layout
ls -l ~/.factory/skills/gstack-qa
# should show: ../../.agents/skills/gstack-qa

test -d ~/.agents/skills/gstack-qa && echo "copied"
# should print: copied

# 3. Verify lockfile
python3 -c "
import json
with open('$HOME/.agents/.skill-lock.json') as f:
    lock = json.load(f)
for name, entry in lock['skills'].items():
    if name.startswith('gstack'):
        print(f'{name}: sourceType={entry[\"sourceType\"]}')"

# 4. If ~/.agents/.skill-lock.json does not exist yet, setup prints a warning
# and skips registration. In that case, start Droid once so it creates the
# lockfile, then rerun:
#   cd ~/gstack && ./setup --host factory

# 5. Restart Droid
# Type /qa in Droid — skill now appears ✓

morluto added 3 commits March 30, 2026 17:29
…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
@morluto morluto marked this pull request as ready for review March 30, 2026 09:54
@morluto morluto marked this pull request as draft March 30, 2026 10:03
@morluto morluto closed this Mar 30, 2026
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.

1 participant