From 8d060670c7deffac098a5cae4820a4878d9eca98 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Thu, 16 Jul 2026 06:53:43 +0800 Subject: [PATCH] fix(setup): install Factory skills in documented root --- setup | 17 ++++++++++------- test/gen-skill-docs.test.ts | 13 +++++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/setup b/setup index 275236cd3..fed75c734 100755 --- a/setup +++ b/setup @@ -912,7 +912,7 @@ link_factory_skill_dirs() { local gstack_dir="$1" local skills_dir="$2" local factory_dir="$gstack_dir/.factory/skills" - local linked=() + local installed=() if [ ! -d "$factory_dir" ]; then echo " Generating .factory/ skill docs..." @@ -929,14 +929,17 @@ link_factory_skill_dirs() { skill_name="$(basename "$skill_dir")" [ "$skill_name" = "gstack" ] && continue target="$skills_dir/$skill_name" - if [ -L "$target" ] || [ ! -e "$target" ]; then - _link_or_copy "$skill_dir" "$target" - linked+=("$skill_name") - fi + # Factory documents ~/.factory/skills as its user-level skill root. Keep + # the install self-contained there and use a real directory so Droid does + # not have to follow a directory symlink during discovery. These gstack-* + # paths are installer-owned and are refreshed on every setup run. + rm -rf "$target" + cp -R "$skill_dir" "$target" + installed+=("$skill_name") fi done - if [ ${#linked[@]} -gt 0 ]; then - echo " linked skills: ${linked[*]}" + if [ ${#installed[@]} -gt 0 ]; then + echo " installed skills: ${installed[*]}" fi } diff --git a/test/gen-skill-docs.test.ts b/test/gen-skill-docs.test.ts index 2fb783ffd..94171de11 100644 --- a/test/gen-skill-docs.test.ts +++ b/test/gen-skill-docs.test.ts @@ -2450,6 +2450,19 @@ describe('setup script validation', () => { expect(fnBody).not.toContain('_link_or_copy "$gstack_dir" "$codex_gstack"'); }); + test('Factory installs real skill directories only in its documented user root', () => { + const fnStart = setupContent.indexOf('link_factory_skill_dirs()'); + const fnEnd = setupContent.indexOf('\nlink_opencode_skill_dirs()', fnStart); + const fnBody = setupContent.slice(fnStart, fnEnd); + + expect(fnBody).toContain('target="$skills_dir/$skill_name"'); + expect(fnBody).toContain('rm -rf "$target"'); + expect(fnBody).toContain('cp -R "$skill_dir" "$target"'); + expect(fnBody).not.toContain('.agents'); + expect(fnBody).not.toContain('.skill-lock.json'); + expect(fnBody).not.toContain('_link_or_copy "$skill_dir" "$target"'); + }); + test('direct Codex installs are migrated out of ~/.codex/skills/gstack', () => { expect(setupContent).toContain('migrate_direct_codex_install'); expect(setupContent).toContain('$HOME/.gstack/repos/gstack');