@@ -74,6 +74,20 @@ func TestCheckSkillLink(t *testing.T) {
7474
7575 assert .Equal (t , "copy" , CheckSkillLink (agentDir , "my-skill" , "/any/source" ))
7676 })
77+
78+ t .Run ("broken on permission error (not missing)" , func (t * testing.T ) {
79+ if os .Getuid () == 0 {
80+ t .Skip ("root bypasses permission checks" )
81+ }
82+ parent := t .TempDir ()
83+ agentDir := filepath .Join (parent , "locked" )
84+ require .NoError (t , os .MkdirAll (filepath .Join (agentDir , "my-skill" ), 0o755 ))
85+ require .NoError (t , os .Chmod (agentDir , 0o000 ))
86+ t .Cleanup (func () { _ = os .Chmod (agentDir , 0o755 ) })
87+
88+ result := CheckSkillLink (agentDir , "my-skill" , "/any/source" )
89+ assert .Equal (t , "broken" , result )
90+ })
7791}
7892
7993// --- CreateSkillLink ---
@@ -168,18 +182,51 @@ func TestCreateSkillLink(t *testing.T) {
168182 assert .Equal (t , "copy" , CheckSkillLink (agentDir , "my-skill" , src ))
169183 })
170184
171- t .Run ("skips real directory when useCopy is false" , func (t * testing.T ) {
185+ t .Run ("warns and skips real directory when useCopy is false" , func (t * testing.T ) {
172186 agentDir := t .TempDir ()
173187 linkPath := filepath .Join (agentDir , "my-skill" )
174188 require .NoError (t , os .MkdirAll (linkPath , 0o755 ))
175189 require .NoError (t , os .WriteFile (filepath .Join (linkPath , "SKILL.md" ), []byte ("original" ), 0o644 ))
176190
177191 src := makeSkillSource (t )
192+ // Should succeed (skip) but warn to stderr.
178193 require .NoError (t , CreateSkillLink (src , agentDir , "my-skill" , false ))
179194
195+ // Original directory content must be preserved.
180196 data , err := os .ReadFile (filepath .Join (linkPath , "SKILL.md" ))
181197 require .NoError (t , err )
182198 assert .Equal (t , "original" , string (data ), "original directory should be preserved" )
199+ // Entry must still be a real directory, not a symlink.
200+ info , err := os .Lstat (linkPath )
201+ require .NoError (t , err )
202+ assert .Zero (t , info .Mode ()& os .ModeSymlink , "entry should remain a directory" )
203+ })
204+
205+ t .Run ("errors on regular file at linkPath" , func (t * testing.T ) {
206+ agentDir := t .TempDir ()
207+ linkPath := filepath .Join (agentDir , "my-skill" )
208+ require .NoError (t , os .WriteFile (linkPath , []byte ("not a dir" ), 0o644 ))
209+
210+ src := makeSkillSource (t )
211+ err := CreateSkillLink (src , agentDir , "my-skill" , false )
212+ assert .Error (t , err )
213+ })
214+
215+ t .Run ("copy is replaced on re-install (replace semantics)" , func (t * testing.T ) {
216+ src := makeSkillSource (t )
217+ agentDir := t .TempDir ()
218+
219+ require .NoError (t , CreateSkillLink (src , agentDir , "my-skill" , true ))
220+
221+ // Add a stale file directly into the copy.
222+ staleFile := filepath .Join (agentDir , "my-skill" , "stale.txt" )
223+ require .NoError (t , os .WriteFile (staleFile , []byte ("stale" ), 0o644 ))
224+
225+ // Re-run copy install; the stale file should be gone.
226+ require .NoError (t , CreateSkillLink (src , agentDir , "my-skill" , true ))
227+
228+ _ , err := os .Stat (staleFile )
229+ assert .True (t , os .IsNotExist (err ), "stale file should be removed after re-install" )
183230 })
184231}
185232
0 commit comments