@@ -192,7 +192,7 @@ describe('genericSkillsOutputPlugin', () => {
192192 } )
193193
194194 describe ( 'registerProjectOutputDirs' , ( ) => {
195- it ( 'should register . skills directory for each project' , async ( ) => {
195+ it ( 'should register both .agents/ skills and legacy .skills directories for each project' , async ( ) => {
196196 const ctx = createMockOutputPluginContext ( {
197197 workspace : {
198198 directory : createMockRelativePath ( '.' , mockWorkspaceDir ) ,
@@ -206,9 +206,11 @@ describe('genericSkillsOutputPlugin', () => {
206206
207207 const results = await plugin . registerProjectOutputDirs ( ctx )
208208
209- expect ( results ) . toHaveLength ( 2 )
210- expect ( results [ 0 ] ?. path ) . toBe ( path . join ( 'project1' , '.skills' ) )
211- expect ( results [ 1 ] ?. path ) . toBe ( path . join ( 'project2' , '.skills' ) )
209+ expect ( results ) . toHaveLength ( 4 ) // Each project should register 2 directories: .agents/skills and .skills
210+ expect ( results [ 0 ] ?. path ) . toBe ( path . join ( 'project1' , '.agents' , 'skills' ) )
211+ expect ( results [ 1 ] ?. path ) . toBe ( path . join ( 'project1' , '.skills' ) )
212+ expect ( results [ 2 ] ?. path ) . toBe ( path . join ( 'project2' , '.agents' , 'skills' ) )
213+ expect ( results [ 3 ] ?. path ) . toBe ( path . join ( 'project2' , '.skills' ) )
212214 } )
213215
214216 it ( 'should return empty array when no skills exist' , async ( ) => {
@@ -225,7 +227,7 @@ describe('genericSkillsOutputPlugin', () => {
225227 } )
226228
227229 describe ( 'registerGlobalOutputDirs' , ( ) => {
228- it ( 'should register ~/.skills/ directory when skills exist ' , async ( ) => {
230+ it ( 'should return empty array (no global output dirs) ' , async ( ) => {
229231 const ctx = createMockOutputPluginContext ( {
230232 workspace : {
231233 directory : createMockRelativePath ( '.' , mockWorkspaceDir ) ,
@@ -236,28 +238,28 @@ describe('genericSkillsOutputPlugin', () => {
236238
237239 const results = await plugin . registerGlobalOutputDirs ( ctx )
238240
239- expect ( results ) . toHaveLength ( 1 )
240- const pathValue = results [ 0 ] ?. path . replaceAll ( '\\' , '/' )
241- const expected = path . join ( '.aindex' , '.skills' ) . replaceAll ( '\\' , '/' )
242- expect ( pathValue ) . toBe ( expected )
243- expect ( results [ 0 ] ?. basePath ) . toBe ( mockHomeDir )
241+ expect ( results ) . toHaveLength ( 0 )
244242 } )
243+ } )
245244
246- it ( 'should return empty array when no skills exist' , async ( ) => {
245+ describe ( 'registerGlobalOutputFiles' , ( ) => {
246+ it ( 'should return empty array (no global output files)' , async ( ) => {
247247 const ctx = createMockOutputPluginContext ( {
248248 workspace : {
249249 directory : createMockRelativePath ( '.' , mockWorkspaceDir ) ,
250250 projects : [ { name : 'project1' , dirFromWorkspacePath : createMockRelativePath ( 'project1' , mockWorkspaceDir ) } ]
251- }
251+ } ,
252+ skills : [ createMockSkillPrompt ( 'test-skill' , 'content' ) ]
252253 } )
253254
254- const results = await plugin . registerGlobalOutputDirs ( ctx )
255+ const results = await plugin . registerGlobalOutputFiles ( ctx )
256+
255257 expect ( results ) . toHaveLength ( 0 )
256258 } )
257259 } )
258260
259- describe ( 'registerGlobalOutputFiles ' , ( ) => {
260- it ( 'should register SKILL.md in ~/.skills/ for each skill' , async ( ) => {
261+ describe ( 'registerProjectOutputFiles ' , ( ) => {
262+ it ( 'should register skill files for each skill in each project ' , async ( ) => {
261263 const ctx = createMockOutputPluginContext ( {
262264 workspace : {
263265 directory : createMockRelativePath ( '.' , mockWorkspaceDir ) ,
@@ -269,12 +271,11 @@ describe('genericSkillsOutputPlugin', () => {
269271 ]
270272 } )
271273
272- const results = await plugin . registerGlobalOutputFiles ( ctx )
274+ const results = await plugin . registerProjectOutputFiles ( ctx )
273275
274- expect ( results ) . toHaveLength ( 2 )
275- expect ( results [ 0 ] ?. path ) . toBe ( path . join ( '.aindex' , '.skills' , 'skill-a' , 'SKILL.md' ) )
276- expect ( results [ 1 ] ?. path ) . toBe ( path . join ( '.aindex' , '.skills' , 'skill-b' , 'SKILL.md' ) )
277- expect ( results [ 0 ] ?. basePath ) . toBe ( mockHomeDir )
276+ expect ( results ) . toHaveLength ( 2 ) // 2 skills * 1 file each = 2 files
277+ expect ( results [ 0 ] ?. path ) . toBe ( path . join ( '.agents' , 'skills' , 'skill-a' , 'SKILL.md' ) )
278+ expect ( results [ 1 ] ?. path ) . toBe ( path . join ( '.agents' , 'skills' , 'skill-b' , 'SKILL.md' ) )
278279 } )
279280
280281 it ( 'should register mcp.json when skill has MCP config' , async ( ) => {
@@ -286,66 +287,52 @@ describe('genericSkillsOutputPlugin', () => {
286287 skills : [ createMockSkillPrompt ( 'test-skill' , 'content' , { mcpConfig : { rawContent : '{}' } } ) ]
287288 } )
288289
289- const results = await plugin . registerGlobalOutputFiles ( ctx )
290+ const results = await plugin . registerProjectOutputFiles ( ctx )
290291
291292 expect ( results ) . toHaveLength ( 2 )
292- expect ( results [ 0 ] ?. path ) . toBe ( path . join ( '.aindex ' , '. skills' , 'test-skill' , 'SKILL.md' ) )
293- expect ( results [ 1 ] ?. path ) . toBe ( path . join ( '.aindex ' , '. skills' , 'test-skill' , 'mcp.json' ) )
293+ expect ( results [ 0 ] ?. path ) . toBe ( path . join ( '.agents ' , 'skills' , 'test-skill' , 'SKILL.md' ) )
294+ expect ( results [ 1 ] ?. path ) . toBe ( path . join ( '.agents ' , 'skills' , 'test-skill' , 'mcp.json' ) )
294295 } )
295- } )
296296
297- describe ( 'writeGlobalOutputs' , ( ) => {
298- it ( 'should write SKILL.md to ~/.skills/ with front matter' , async ( ) => {
299- const ctx = createMockOutputWriteContext ( {
297+ it ( 'should register child docs' , async ( ) => {
298+ const ctx = createMockOutputPluginContext ( {
300299 workspace : {
301300 directory : createMockRelativePath ( '.' , mockWorkspaceDir ) ,
302301 projects : [ { name : 'project1' , dirFromWorkspacePath : createMockRelativePath ( 'project1' , mockWorkspaceDir ) } ]
303302 } ,
304- skills : [ createMockSkillPrompt ( 'test-skill' , '# Skill Content' , {
305- description : 'A test skill' ,
306- keywords : [ 'test' , 'demo' ]
303+ skills : [ createMockSkillPrompt ( 'test-skill' , 'content' , {
304+ childDocs : [ { relativePath : 'doc1.mdx' , content : 'doc content' } ]
307305 } ) ]
308306 } )
309307
310- const results = await plugin . writeGlobalOutputs ( ctx )
311-
312- expect ( results . files ) . toHaveLength ( 1 )
313- expect ( results . files [ 0 ] ?. success ) . toBe ( true )
314-
315- const writeCall = vi . mocked ( fs . writeFileSync ) . mock . calls [ 0 ]
316- expect ( writeCall ) . toBeDefined ( )
317- expect ( writeCall ?. [ 0 ] ) . toContain ( path . join ( mockHomeDir , '.aindex' , '.skills' , 'test-skill' , 'SKILL.md' ) )
308+ const results = await plugin . registerProjectOutputFiles ( ctx )
318309
319- const writtenContent = writeCall ?. [ 1 ] as string
320- expect ( writtenContent ) . toContain ( 'name: test-skill' )
321- expect ( writtenContent ) . toContain ( 'description: A test skill' )
322- expect ( writtenContent ) . toContain ( '# Skill Content' )
310+ expect ( results ) . toHaveLength ( 2 )
311+ expect ( results [ 0 ] ?. path ) . toBe ( path . join ( '.agents' , 'skills' , 'test-skill' , 'SKILL.md' ) )
312+ expect ( results [ 1 ] ?. path ) . toBe ( path . join ( '.agents' , 'skills' , 'test-skill' , 'doc1.md' ) )
323313 } )
324314
325- it ( 'should support dry-run mode' , async ( ) => {
326- const ctx = createMockOutputWriteContext (
327- {
328- workspace : {
329- directory : createMockRelativePath ( '.' , mockWorkspaceDir ) ,
330- projects : [ { name : 'project1' , dirFromWorkspacePath : createMockRelativePath ( 'project1' , mockWorkspaceDir ) } ]
331- } ,
332- skills : [ createMockSkillPrompt ( 'test-skill' , 'content' ) ]
315+ it ( 'should register resources' , async ( ) => {
316+ const ctx = createMockOutputPluginContext ( {
317+ workspace : {
318+ directory : createMockRelativePath ( '.' , mockWorkspaceDir ) ,
319+ projects : [ { name : 'project1' , dirFromWorkspacePath : createMockRelativePath ( 'project1' , mockWorkspaceDir ) } ]
333320 } ,
334- true
335- )
321+ skills : [ createMockSkillPrompt ( 'test-skill' , 'content' , {
322+ resources : [ { relativePath : 'resource.json' , content : '{}' , encoding : 'text' } ]
323+ } ) ]
324+ } )
336325
337- const results = await plugin . writeGlobalOutputs ( ctx )
326+ const results = await plugin . registerProjectOutputFiles ( ctx )
338327
339- expect ( results . files ) . toHaveLength ( 1 )
340- expect ( results . files [ 0 ] ?. success ) . toBe ( true )
341- expect ( results . files [ 0 ] ?. skipped ) . toBe ( false )
342- expect ( vi . mocked ( fs . writeFileSync ) ) . not . toHaveBeenCalled ( )
328+ expect ( results ) . toHaveLength ( 2 )
329+ expect ( results [ 0 ] ?. path ) . toBe ( path . join ( '.agents' , 'skills' , 'test-skill' , 'SKILL.md' ) )
330+ expect ( results [ 1 ] ?. path ) . toBe ( path . join ( '.agents' , 'skills' , 'test-skill' , 'resource.json' ) )
343331 } )
344332 } )
345333
346334 describe ( 'writeProjectOutputs' , ( ) => {
347- it ( 'should create symlinks for each skill in each project' , async ( ) => {
348- vi . mocked ( fs . existsSync ) . mockReturnValue ( false ) // Symlink doesn't exist yet
335+ it ( 'should write skill files directly to project directory' , async ( ) => {
349336 const ctx = createMockOutputWriteContext ( {
350337 workspace : {
351338 directory : createMockRelativePath ( '.' , mockWorkspaceDir ) ,
@@ -359,22 +346,19 @@ describe('genericSkillsOutputPlugin', () => {
359346
360347 const results = await plugin . writeProjectOutputs ( ctx )
361348
362- expect ( results . files ) . toHaveLength ( 2 )
363- expect ( vi . mocked ( fs . symlinkSync ) ) . toHaveBeenCalledTimes ( 2 )
349+ expect ( results . files ) . toHaveLength ( 2 ) // 2 projects * 1 skill = 2 files
350+ expect ( results . files [ 0 ] ?. success ) . toBe ( true )
351+ expect ( results . files [ 1 ] ?. success ) . toBe ( true )
364352
365- expect ( vi . mocked ( fs . symlinkSync ) ) . toHaveBeenCalledWith ( // Verify symlinks point from project to global
366- expect . stringContaining ( path . join ( mockHomeDir , '.aindex' , '.skills' , 'test-skill' ) ) ,
367- expect . stringContaining ( path . join ( 'project1' , '.skills' , 'test-skill' ) ) ,
368- expect . anything ( )
369- )
370- expect ( vi . mocked ( fs . symlinkSync ) ) . toHaveBeenCalledWith (
371- expect . stringContaining ( path . join ( mockHomeDir , '.aindex' , '.skills' , 'test-skill' ) ) ,
372- expect . stringContaining ( path . join ( 'project2' , '.skills' , 'test-skill' ) ) ,
373- expect . anything ( )
374- )
353+ expect ( vi . mocked ( fs . writeFileSync ) ) . toHaveBeenCalled ( ) // Verify files are written (not symlinks created)
354+ expect ( vi . mocked ( fs . symlinkSync ) ) . not . toHaveBeenCalled ( )
355+
356+ const writeCalls = vi . mocked ( fs . writeFileSync ) . mock . calls // Verify correct paths
357+ expect ( writeCalls [ 0 ] ?. [ 0 ] ) . toContain ( path . join ( 'project1' , '.agents' , 'skills' , 'test-skill' , 'SKILL.md' ) )
358+ expect ( writeCalls [ 1 ] ?. [ 0 ] ) . toContain ( path . join ( 'project2' , '.agents' , 'skills' , 'test-skill' , 'SKILL.md' ) )
375359 } )
376360
377- it ( 'should support dry-run mode for symlinks ' , async ( ) => {
361+ it ( 'should support dry-run mode' , async ( ) => {
378362 const ctx = createMockOutputWriteContext (
379363 {
380364 workspace : {
@@ -390,8 +374,7 @@ describe('genericSkillsOutputPlugin', () => {
390374
391375 expect ( results . files ) . toHaveLength ( 1 )
392376 expect ( results . files [ 0 ] ?. success ) . toBe ( true )
393- expect ( results . files [ 0 ] ?. skipped ) . toBe ( false )
394- expect ( vi . mocked ( fs . symlinkSync ) ) . not . toHaveBeenCalled ( )
377+ expect ( vi . mocked ( fs . writeFileSync ) ) . not . toHaveBeenCalled ( )
395378 } )
396379
397380 it ( 'should skip project without dirFromWorkspacePath' , async ( ) => {
@@ -406,7 +389,6 @@ describe('genericSkillsOutputPlugin', () => {
406389 const results = await plugin . writeProjectOutputs ( ctx )
407390
408391 expect ( results . files ) . toHaveLength ( 0 )
409- expect ( vi . mocked ( fs . symlinkSync ) ) . not . toHaveBeenCalled ( )
410392 } )
411393
412394 it ( 'should return empty results when no skills exist' , async ( ) => {
@@ -422,26 +404,47 @@ describe('genericSkillsOutputPlugin', () => {
422404 expect ( results . files ) . toHaveLength ( 0 )
423405 expect ( results . dirs ) . toHaveLength ( 0 )
424406 } )
407+
408+ it ( 'should write skill with front matter' , async ( ) => {
409+ const ctx = createMockOutputWriteContext ( {
410+ workspace : {
411+ directory : createMockRelativePath ( '.' , mockWorkspaceDir ) ,
412+ projects : [ { name : 'project1' , dirFromWorkspacePath : createMockRelativePath ( 'project1' , mockWorkspaceDir ) } ]
413+ } ,
414+ skills : [ createMockSkillPrompt ( 'test-skill' , '# Skill Content' , {
415+ description : 'A test skill' ,
416+ keywords : [ 'test' , 'demo' ]
417+ } ) ]
418+ } )
419+
420+ await plugin . writeProjectOutputs ( ctx )
421+
422+ const writeCall = vi . mocked ( fs . writeFileSync ) . mock . calls [ 0 ]
423+ expect ( writeCall ) . toBeDefined ( )
424+ expect ( writeCall ?. [ 0 ] ) . toContain ( path . join ( 'project1' , '.agents' , 'skills' , 'test-skill' , 'SKILL.md' ) )
425+
426+ const writtenContent = writeCall ?. [ 1 ] as string
427+ expect ( writtenContent ) . toContain ( 'name: test-skill' )
428+ expect ( writtenContent ) . toContain ( 'description: A test skill' )
429+ expect ( writtenContent ) . toContain ( '# Skill Content' )
430+ } )
425431 } )
426432
427- describe ( 'registerProjectOutputFiles ' , ( ) => {
428- it ( 'should register symlink paths for each skill in each project ' , async ( ) => {
429- const ctx = createMockOutputPluginContext ( {
433+ describe ( 'writeGlobalOutputs ' , ( ) => {
434+ it ( 'should return empty results (no global output) ' , async ( ) => {
435+ const ctx = createMockOutputWriteContext ( {
430436 workspace : {
431437 directory : createMockRelativePath ( '.' , mockWorkspaceDir ) ,
432438 projects : [ { name : 'project1' , dirFromWorkspacePath : createMockRelativePath ( 'project1' , mockWorkspaceDir ) } ]
433439 } ,
434- skills : [
435- createMockSkillPrompt ( 'skill-a' , 'content a' ) ,
436- createMockSkillPrompt ( 'skill-b' , 'content b' )
437- ]
440+ skills : [ createMockSkillPrompt ( 'test-skill' , 'content' ) ]
438441 } )
439442
440- const results = await plugin . registerProjectOutputFiles ( ctx )
443+ const results = await plugin . writeGlobalOutputs ( ctx )
441444
442- expect ( results ) . toHaveLength ( 2 )
443- expect ( results [ 0 ] ?. path ) . toBe ( path . join ( '.skills' , 'skill-a' ) )
444- expect ( results [ 1 ] ?. path ) . toBe ( path . join ( '.skills' , 'skill-b' ) )
445+ expect ( results . files ) . toHaveLength ( 0 )
446+ expect ( results . dirs ) . toHaveLength ( 0 )
447+ expect ( vi . mocked ( fs . writeFileSync ) ) . not . toHaveBeenCalled ( )
445448 } )
446449 } )
447450} )
0 commit comments