@@ -16,6 +16,7 @@ import {
1616 parseSkillDependencies ,
1717 parseSkillFrontmatter ,
1818} from "./parse-skill-frontmatter" ;
19+ import { parseSkillReferences } from "./parse-skill-references" ;
1920import type {
2021 BundleLocalSkillInput ,
2122 BundleLocalSkillOutput ,
@@ -506,27 +507,43 @@ export class SkillsService {
506507 }
507508
508509 /**
509- * Expand a set of tagged skill refs to include their transitively-declared
510- * dependency skills (SKILL.md `dependencies:`) , so a skill that needs another
511- * (e.g. `/rs-self-review` → `rs-adversarial-review`) pulls its dependency into
512- * the same cloud run instead of the user having to tag every one by hand .
513- * Only uploadable local skills are returned; a dependency that resolves to a
514- * built-in (`bundled`) skill is already present in the sandbox and is skipped.
510+ * Expand a set of tagged skill refs to include their transitive dependency
511+ * skills, so a skill that needs another pulls it into the same cloud run.
512+ * A dependency is either declared (SKILL.md frontmatter `dependencies:`)
513+ * or referenced in the SKILL.md body as `/skill-name` or `[[skill-name]]` .
514+ * Only uploadable local skills are returned; a dependency that resolves to
515+ * a built-in (`bundled`) skill is already in the sandbox and is skipped.
515516 */
516517 async resolveSkillBundleDependencies (
517518 refs : SkillBundleRef [ ] ,
518519 ) : Promise < SkillBundleRef [ ] > {
519520 if ( refs . length === 0 ) return [ ] ;
520521
521522 const allSkills = await this . listSkills ( ) ;
522- const findUploadableByName = ( name : string ) : SkillBundleRef | null => {
523- const match = allSkills . find (
523+ // Prefer a dependency beside the referencing skill (same root), then one
524+ // from the same source, so a repo skill referencing /helper gets its
525+ // sibling rather than a same-named skill from another source.
526+ const findUploadableByName = (
527+ name : string ,
528+ referencedFrom : SkillBundleRef ,
529+ ) : SkillBundleRef | null => {
530+ const candidates = allSkills . filter (
524531 ( skill ) => skill . name === name && isUploadableSkillSource ( skill . source ) ,
525532 ) ;
533+ const match =
534+ candidates . find (
535+ ( skill ) =>
536+ path . dirname ( skill . path ) === path . dirname ( referencedFrom . path ) ,
537+ ) ??
538+ candidates . find ( ( skill ) => skill . source === referencedFrom . source ) ??
539+ candidates [ 0 ] ;
526540 return match && isUploadableSkillSource ( match . source )
527541 ? { name : match . name , source : match . source , path : match . path }
528542 : null ;
529543 } ;
544+ const knownSkillNames : ReadonlySet < string > = new Set (
545+ allSkills . map ( ( skill ) => skill . name ) ,
546+ ) ;
530547
531548 const seen = new Set < string > ( ) ;
532549 const resolved : SkillBundleRef [ ] = [ ] ;
@@ -558,15 +575,20 @@ export class SkillsService {
558575 path . join ( skillDir , "SKILL.md" ) ,
559576 "utf-8" ,
560577 ) ;
561- dependencyNames = parseSkillDependencies ( manifest ) ;
578+ dependencyNames = [
579+ ...new Set ( [
580+ ...parseSkillDependencies ( manifest ) ,
581+ ...parseSkillReferences ( manifest , knownSkillNames ) ,
582+ ] ) ,
583+ ] ;
562584 } catch {
563585 // A ref we can't read (missing/renamed skill) still uploads on its own;
564586 // just skip its dependency expansion rather than failing the whole run.
565587 continue ;
566588 }
567589
568590 for ( const dependencyName of dependencyNames ) {
569- const dependencyRef = findUploadableByName ( dependencyName ) ;
591+ const dependencyRef = findUploadableByName ( dependencyName , ref ) ;
570592 if (
571593 dependencyRef &&
572594 ! seen . has ( `${ dependencyRef . source } :${ dependencyRef . path } ` )
0 commit comments