@@ -1247,6 +1247,72 @@ describe("resolveAndFetchSources", () => {
12471247 ) ;
12481248 } ) ;
12491249
1250+ it ( "installs the root SKILL.md under the requested name when real skill dirs coexist and the requested skill is absent" , async ( ) => {
1251+ // Locks the (intended) widened-fallback behavior: when the repository has
1252+ // real skill dirs plus an incidental root SKILL.md and the caller requests a
1253+ // skill name that matches no dir, the root is installed under the requested
1254+ // name.
1255+ mockClientInstance . listDirectory . mockImplementation (
1256+ async ( _owner : string , _repo : string , path : string ) => {
1257+ if ( path === "." ) {
1258+ return [
1259+ { name : "other-skill" , path : "other-skill" , type : "dir" , size : 0 } ,
1260+ { name : "SKILL.md" , path : "SKILL.md" , type : "file" , size : 50 } ,
1261+ { name : "README.md" , path : "README.md" , type : "file" , size : 20 } ,
1262+ ] ;
1263+ }
1264+ return [ ] ;
1265+ } ,
1266+ ) ;
1267+ mockClientInstance . getFileContent . mockImplementation (
1268+ async ( _o : string , _r : string , path : string ) => {
1269+ if ( path === "SKILL.md" ) return "# Root Skill" ;
1270+ if ( path === "README.md" ) return "docs" ;
1271+ return "" ;
1272+ } ,
1273+ ) ;
1274+
1275+ const result = await resolveAndFetchSources ( {
1276+ logger,
1277+ sources : [ { source : "org/repo:." , skills : [ "nonexistent" ] } ] ,
1278+ projectRoot : testDir ,
1279+ } ) ;
1280+
1281+ expect ( result . fetchedSkillCount ) . toBe ( 1 ) ;
1282+ expect ( writeFileContent ) . toHaveBeenCalledWith (
1283+ join ( testDir , RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH , "nonexistent" , "SKILL.md" ) ,
1284+ "# Root Skill" ,
1285+ ) ;
1286+ } ) ;
1287+
1288+ it ( "does not fetch root files when the requested skill is absent and there is no root SKILL.md" , async ( ) => {
1289+ // The fallback (and its full root-file fetch) must be short-circuited when the
1290+ // directory listing shows no root SKILL.md — nothing would be installed, so no
1291+ // root content should be fetched.
1292+ mockClientInstance . listDirectory . mockImplementation (
1293+ async ( _owner : string , _repo : string , path : string ) => {
1294+ if ( path === "." ) {
1295+ return [
1296+ { name : "other-skill" , path : "other-skill" , type : "dir" , size : 0 } ,
1297+ { name : "README.md" , path : "README.md" , type : "file" , size : 20 } ,
1298+ ] ;
1299+ }
1300+ return [ ] ;
1301+ } ,
1302+ ) ;
1303+
1304+ const result = await resolveAndFetchSources ( {
1305+ logger,
1306+ sources : [ { source : "org/repo:." , skills : [ "nonexistent" ] } ] ,
1307+ projectRoot : testDir ,
1308+ } ) ;
1309+
1310+ expect ( result . fetchedSkillCount ) . toBe ( 0 ) ;
1311+ expect ( writeFileContent ) . not . toHaveBeenCalled ( ) ;
1312+ // No root file contents are fetched because the fallback is short-circuited.
1313+ expect ( mockClientInstance . getFileContent ) . not . toHaveBeenCalled ( ) ;
1314+ } ) ;
1315+
12501316 it ( "should treat backslash-separated git paths as nested skill files" , async ( ) => {
12511317 const { resolveDefaultRef, fetchSkillFiles } = await import ( "./git-client.js" ) ;
12521318 vi . mocked ( resolveDefaultRef ) . mockResolvedValue ( { ref : "main" , sha : "d" . repeat ( 40 ) } ) ;
0 commit comments