Coverage for src / library_skills / scanner.py: 100%

236 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-03 20:19 +0000

1import csv 2{b~bac|bbccc}bdcec

2import json 2{b~bac|bbccc}bdcec

3import re 2{b~bac|bbccc}bdcec

4from dataclasses import dataclass, field 2{b~bac|bbccc}bdcec

5from email.parser import Parser 2{b~bac|bbccc}bdcec

6from pathlib import Path, PurePosixPath 2{b~bac|bbccc}bdcec

7from urllib.parse import urlparse 2{b~bac|bbccc}bdcec

8from urllib.request import url2pathname 2{b~bac|bbccc}bdcec

9 

10import yaml 2{b~bac|bbccc}bdcec

11 

12 

13@dataclass(frozen=True) 2{b~bac|bbccc}bdcec

14class Skill: 2{b~bac|bbccc}bdcec

15 """A discovered agent skill.""" 

16 

17 name: str 2{b|b}b

18 description: str 2{b|b}b

19 path: Path 2{b|b}b

20 package_name: str 2{b|b}b

21 skill_dir: Path 2{b|b}b

22 package_version: str = "" 2{b~bac|bbccc}bdcec

23 

24 

25@dataclass 2{b~bac|bbccc}bdcec

26class ScanResult: 2{b~bac|bbccc}bdcec

27 """Result of scanning for skills.""" 

28 

29 skills: list[Skill] = field(default_factory=list) 2{b~bac|bbccc}bdcec

30 warnings: list[str] = field(default_factory=list) 2{b~bac|bbccc}bdcec

31 environment_path: Path | None = None 2{b~bac|bbccc}bdcec

32 

33 

34_SKILL_NAME_RE = re.compile(r"^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$") 2{b~bac|bbccc}bdcec

35 

36 

37def scan_python_distributions(site_packages: Path) -> ScanResult: 2{b~bac|bbccc}bdcec

38 """Scan target environment distribution metadata for bundled skills.""" 

39 result = ScanResult() 2N !c#c$cd e O P Q R %cnb'cobS T U pbqb(c)cV W *cX Y +crbsbtbub,cZ a -c0 1 2 3 .c4 5 p @bvbf K wbYcm AcE RbF B v s XbUb0by xc/c1b6 7 8 :c;c=cg h 9 ! # $ ?cxb@cyb% ' ( zbAb[c]c) * ^c+ , _cBbCbDbEb`c- b {c. / : ; |c= ? q [bFbi L GbZcn BcG SbH C w t YbVb2bz yc}c3b@ [ ] ~cadbdj k ^ _ ` { cdHbddIb| } ~ JbKbedfdabbbgdcbdbhdLbMbNbObidebc jdfbgbhbibkdjbkbr ]bPbl M Qb0co CcI TbJ D x u ZbWb4bA zcld5blbmb

40 seen_skill_dirs: set[Path] = set() 2N !c#c$cd e O P Q R %cnb'cobS T U pbqb(c)cV W *cX Y +crbsbtbub,cZ a -c0 1 2 3 .c4 5 p @bvbf K wbYcm AcE RbF B v s XbUb0by xc/c1b6 7 8 :c;c=cg h 9 ! # $ ?cxb@cyb% ' ( zbAb[c]c) * ^c+ , _cBbCbDbEb`c- b {c. / : ; |c= ? q [bFbi L GbZcn BcG SbH C w t YbVb2bz yc}c3b@ [ ] ~cadbdj k ^ _ ` { cdHbddIb| } ~ JbKbedfdabbbgdcbdbhdLbMbNbObidebc jdfbgbhbibkdjbkbr ]bPbl M Qb0co CcI TbJ D x u ZbWb4bA zcld5blbmb

41 

42 if not site_packages.is_dir(): 2N !c#c$cd e O P Q R %cnb'cobS T U pbqb(c)cV W *cX Y +crbsbtbub,cZ a -c0 1 2 3 .c4 5 p @bvbf K wbYcm AcE RbF B v s XbUb0by xc/c1b6 7 8 :c;c=cg h 9 ! # $ ?cxb@cyb% ' ( zbAb[c]c) * ^c+ , _cBbCbDbEb`c- b {c. / : ; |c= ? q [bFbi L GbZcn BcG SbH C w t YbVb2bz yc}c3b@ [ ] ~cadbdj k ^ _ ` { cdHbddIb| } ~ JbKbedfdabbbgdcbdbhdLbMbNbObidebc jdfbgbhbibkdjbkbr ]bPbl M Qb0co CcI TbJ D x u ZbWb4bA zcld5blbmb

43 result.warnings.append(f"Site-packages directory not found: {site_packages}") 2/cWdXd}cYdZdld0d1d

44 return result 2/cWdXd}cYdZdld0d1d

45 

46 for dist_info in sorted(site_packages.glob("*.dist-info")): 2N !c#c$cd e O P Q R %cnb'cobS T U pbqb(c)cV W *cX Y +crbsbtbub,cZ a -c0 1 2 3 .c4 5 p @bvbf K wbYcm AcE RbF B v s XbUb0by xc1b6 7 8 :c;c=cg h 9 ! # $ ?cxb@cyb% ' ( zbAb[c]c) * ^c+ , _cBbCbDbEb`c- b {c. / : ; |c= ? q [bFbi L GbZcn BcG SbH C w t YbVb2bz yc3b@ [ ] ~cadbdj k ^ _ ` { cdHbddIb| } ~ JbKbedfdabbbgdcbdbhdLbMbNbObidebc jdfbgbhbibkdjbkbr ]bPbl M Qb0co CcI TbJ D x u ZbWb4bA zc5blbmb

47 if not dist_info.is_dir(): 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbYcm AcE RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L GbZcn BcG SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qb0co CcI TbJ D x u ZbWb4bA zc5blbmb

48 continue 2Yc-d.dZc/d:d0c;d=d

49 

50 dist = _read_distribution_info(dist_info) 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm AcE RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn BcG SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo CcI TbJ D x u ZbWb4bA zc5blbmb

51 if dist is None: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm AcE RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn BcG SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo CcI TbJ D x u ZbWb4bA zc5blbmb

52 result.warnings.append( 2AcxcpdqdBcycrdsdCczctdud

53 f"Skipping invalid distribution metadata: {dist_info}" 

54 ) 

55 continue 2AcxcpdqdBcycrdsdCczctdud

56 

57 found = _scan_distribution_records( 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb

58 site_packages=site_packages, 

59 dist_info=dist_info, 

60 package_name=dist.name, 

61 package_version=dist.version, 

62 seen_skill_dirs=seen_skill_dirs, 

63 ) 

64 result.skills.extend(found.skills) 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb

65 result.warnings.extend(found.warnings) 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb

66 

67 if not found.skills: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb

68 fallback = _scan_editable_direct_url( 2p @bf m E RbF B v s XbUb0by 1bDcEcq [bi n G SbH C w t YbVb2bz 3bFcGcr ]bl o I TbJ D x u ZbWb4bA 5bHcIc

69 dist_info=dist_info, 

70 site_packages=site_packages, 

71 package_name=dist.name, 

72 package_version=dist.version, 

73 seen_skill_dirs=seen_skill_dirs, 

74 ) 

75 result.skills.extend(fallback.skills) 2p @bf m E RbF B v s XbUb0by 1bDcEcq [bi n G SbH C w t YbVb2bz 3bFcGcr ]bl o I TbJ D x u ZbWb4bA 5bHcIc

76 result.warnings.extend(fallback.warnings) 2p @bf m E RbF B v s XbUb0by 1bDcEcq [bi n G SbH C w t YbVb2bz 3bFcGcr ]bl o I TbJ D x u ZbWb4bA 5bHcIc

77 

78 return result 2N !c#c$cd e O P Q R %cnb'cobS T U pbqb(c)cV W *cX Y +crbsbtbub,cZ a -c0 1 2 3 .c4 5 p @bvbf K wbYcm AcE RbF B v s XbUb0by xc1b6 7 8 :c;c=cg h 9 ! # $ ?cxb@cyb% ' ( zbAb[c]c) * ^c+ , _cBbCbDbEb`c- b {c. / : ; |c= ? q [bFbi L GbZcn BcG SbH C w t YbVb2bz yc3b@ [ ] ~cadbdj k ^ _ ` { cdHbddIb| } ~ JbKbedfdabbbgdcbdbhdLbMbNbObidebc jdfbgbhbibkdjbkbr ]bPbl M Qb0co CcI TbJ D x u ZbWb4bA zc5blbmb

79 

80 

81def scan_node_packages(node_modules: Path) -> ScanResult: 2{b~bac|bbccc}bdcec

82 """Scan Node.js packages in node_modules for bundled skills.""" 

83 result = ScanResult() 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc

84 seen_skill_dirs: set[Path] = set() 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc

85 

86 if not node_modules.is_dir(): 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc

87 result.warnings.append(f"node_modules directory not found: {node_modules}") 2fcPcQcgcRcSchcTcUc

88 return result 2fcPcQcgcRcSchcTcUc

89 

90 for package_root in _iter_node_package_roots(node_modules): 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc

91 package_info = _read_node_package_info(package_root) 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc

92 if package_info is None: 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc

93 result.warnings.append(f"Skipping invalid package metadata: {package_root}") 2fcPcQcgcRcSchcTcUc

94 continue 2fcPcQcgcRcSchcTcUc

95 

96 for skill_md in sorted(package_root.glob(".agents/skills/*/SKILL.md")): 2d e 6b7b8b9b!ba ^b.b;bicjcg h #b$b%b'b(bb _b/b=bkclcj k )b*b+b,b-bc `b:b?bmcnc

97 skill_dir = skill_md.parent.resolve() 2d e 6b7b8b9b!ba ^b.b;bicjcg h #b$b%b'b(bb _b/b=bkclcj k )b*b+b,b-bc `b:b?bmcnc

98 if skill_dir in seen_skill_dirs: 2d e 6b7b8b9b!ba ^b.b;bicjcg h #b$b%b'b(bb _b/b=bkclcj k )b*b+b,b-bc `b:b?bmcnc

99 continue 2^b?d@d_b[d]d`b^d_d

100 seen_skill_dirs.add(skill_dir) 2d e 6b7b8b9b!ba ^b.b;bicjcg h #b$b%b'b(bb _b/b=bkclcj k )b*b+b,b-bc `b:b?bmcnc

101 

102 skill, warning = _load_skill( 2d e 6b7b8b9b!ba ^b.b;bicjcg h #b$b%b'b(bb _b/b=bkclcj k )b*b+b,b-bc `b:b?bmcnc

103 skill_dir=skill_dir, 

104 skill_md=skill_md.resolve(), 

105 package_name=package_info.name, 

106 package_version=package_info.version, 

107 ) 

108 if skill: 2d e 6b7b8b9b!ba ^b.b;bicjcg h #b$b%b'b(bb _b/b=bkclcj k )b*b+b,b-bc `b:b?bmcnc

109 result.skills.append(skill) 2d e 6b7b8b9b!ba ^b.bicjcg h #b$b%b'b(bb _b/bkclcj k )b*b+b,b-bc `b:bmcnc

110 elif warning: 2;bvdwd=bxdyd?bzdAd

111 result.warnings.append(warning) 2;bvdwd=bxdyd?bzdAd

112 

113 return result 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc

114 

115 

116def _iter_node_package_roots(node_modules: Path) -> list[Path]: 2{b~bac|bbccc}bdcec

117 package_roots: list[Path] = [] 2d e 6b7b8b9b!ba .b;bfcicjcg h #b$b%b'b(bb /b=bgckclcj k )b*b+b,b-bc :b?bhcmcnc

118 for entry in sorted(node_modules.iterdir()): 2d e 6b7b8b9b!ba .b;bfcicjcg h #b$b%b'b(bb /b=bgckclcj k )b*b+b,b-bc :b?bhcmcnc

119 if not entry.is_dir(): 2d e 6b7b8b9b!ba .b;bfcicjcg h #b$b%b'b(bb /b=bgckclcj k )b*b+b,b-bc :b?bhcmcnc

120 continue 2fcPcQcgcRcSchcTcUc

121 if entry.name.startswith("."): 2d e 6b7b8b9b!ba .b;bfcicjcg h #b$b%b'b(bb /b=bgckclcj k )b*b+b,b-bc :b?bhcmcnc

122 continue 2fcPcQcgcRcSchcTcUc

123 if entry.name.startswith("@"): 2d e 6b7b8b9b!ba .b;bfcicjcg h #b$b%b'b(bb /b=bgckclcj k )b*b+b,b-bc :b?bhcmcnc

124 package_roots.extend( 2a .b`d{db /b|d}dc :b~dae

125 child for child in sorted(entry.iterdir()) if child.is_dir() 

126 ) 

127 else: 

128 package_roots.append(entry) 2d e 6b7b8b9b!ba ;bfcicjcg h #b$b%b'b(bb =bgckclcj k )b*b+b,b-bc ?bhcmcnc

129 return package_roots 2d e 6b7b8b9b!ba .b;bfcicjcg h #b$b%b'b(bb /b=bgckclcj k )b*b+b,b-bc :b?bhcmcnc

130 

131 

132@dataclass(frozen=True) 2{b~bac|bbccc}bdcec

133class _DistributionInfo: 2{b~bac|bbccc}bdcec

134 name: str 2{b|b}b

135 version: str 2{b|b}b

136 

137 

138_PackageInfo = _DistributionInfo 2{b~bac|bbccc}bdcec

139 

140 

141def _read_node_package_info(package_root: Path) -> _PackageInfo | None: 2{b~bac|bbccc}bdcec

142 package_json = package_root / "package.json" 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc

143 try: 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc

144 data = json.loads(package_json.read_text(encoding="utf-8")) 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc

145 except (OSError, json.JSONDecodeError): 2fcPcQcgcRcSchcTcUc

146 return None 2fcPcQcgcRcSchcTcUc

147 

148 if not isinstance(data, dict): 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc

149 return None 2fcPcQcgcRcSchcTcUc

150 name = data.get("name") 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc

151 if not isinstance(name, str) or not name: 2d e 6b7b8b9b!ba ^b.b;bfcicjcg h #b$b%b'b(bb _b/b=bgckclcj k )b*b+b,b-bc `b:b?bhcmcnc

152 return None 2fcPcQcgcRcSchcTcUc

153 version = data.get("version") 2d e 6b7b8b9b!ba ^b.b;bicjcg h #b$b%b'b(bb _b/b=bkclcj k )b*b+b,b-bc `b:b?bmcnc

154 return _PackageInfo(name=name, version=version if isinstance(version, str) else "") 2d e 6b7b8b9b!ba ^b.b;bicjcg h #b$b%b'b(bb _b/b=bkclcj k )b*b+b,b-bc `b:b?bmcnc

155 

156 

157def _read_distribution_info(dist_info: Path) -> _DistributionInfo | None: 2{b~bac|bbccc}bdcec

158 metadata_path = dist_info / "METADATA" 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm AcE RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn BcG SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo CcI TbJ D x u ZbWb4bA zc5blbmb

159 try: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm AcE RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn BcG SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo CcI TbJ D x u ZbWb4bA zc5blbmb

160 metadata_text = metadata_path.read_text(encoding="utf-8") 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm AcE RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn BcG SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo CcI TbJ D x u ZbWb4bA zc5blbmb

161 except OSError: 2AcpdqdBcrdsdCctdud

162 return None 2AcpdqdBcrdsdCctdud

163 

164 metadata = Parser().parsestr(metadata_text) 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA zc5blbmb

165 name = metadata.get("Name") 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA zc5blbmb

166 if not name: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by xc1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz yc3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA zc5blbmb

167 return None 2xcbeceycdeeezcfege

168 return _DistributionInfo(name=name, version=metadata.get("Version", "")) 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb

169 

170 

171def _scan_distribution_records( 2{b~bac|bbccc}bdcec

172 *, 

173 site_packages: Path, 

174 dist_info: Path, 

175 package_name: str, 

176 package_version: str, 

177 seen_skill_dirs: set[Path], 

178) -> ScanResult: 

179 result = ScanResult() 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb

180 record_path = dist_info / "RECORD" 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb

181 try: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb

182 record_file = record_path.open(encoding="utf-8", newline="") 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 p @bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? q [bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbr ]bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb

183 except OSError: 2p @bf m ocpcq [bi n qcrcr ]bl o sctc

184 return result 2p @bf m ocpcq [bi n qcrcr ]bl o sctc

185 

186 with record_file: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb

187 for row in csv.reader(record_file): 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb

188 if not row: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb

189 continue 2K BdCdL DdEdM FdGd

190 installed_path = row[0] 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb

191 if not _is_skill_file_record(installed_path): 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb

192 continue 2K BdCdL DdEdM FdGd

193 

194 skill_md = (site_packages / installed_path).resolve() 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb

195 skill_dir = skill_md.parent 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb

196 if skill_dir in seen_skill_dirs: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb

197 continue 2K BdCdL DdEdM FdGd

198 seen_skill_dirs.add(skill_dir) 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb

199 

200 skill, warning = _load_skill( 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb

201 skill_dir=skill_dir, 

202 skill_md=skill_md, 

203 package_name=package_name, 

204 package_version=package_version, 

205 ) 

206 if skill: 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb

207 result.skills.append(skill) 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wb6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L Gb@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M Qblbmb

208 elif warning: 2E RbF B v s XbUb0by 1bDcEcG SbH C w t YbVb2bz 3bFcGcI TbJ D x u ZbWb4bA 5bHcIc

209 result.warnings.append(warning) 2E RbF B v s XbUb0by 1bDcEcG SbH C w t YbVb2bz 3bFcGcI TbJ D x u ZbWb4bA 5bHcIc

210 

211 return result 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb

212 

213 

214def _is_skill_file_record(installed_path: str) -> bool: 2{b~bac|bbccc}bdcec

215 parts = PurePosixPath(installed_path).parts 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb

216 for index, part in enumerate(parts): 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb

217 if part != ".agents": 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb

218 continue 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb

219 if ( 

220 len(parts) > index + 3 

221 and parts[index + 1] == "skills" 

222 and parts[-1] == "SKILL.md" 

223 ): 

224 return True 2N d e O P Q R nbobS T U pbqbV W X Y rbsbtbubZ a 0 1 2 3 4 5 vbf K wbE RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , BbCbDbEb- b . / : ; = ? Fbi L GbG SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdbLbMbNbObebc fbgbhbibjbkbPbl M QbI TbJ D x u ZbWb4bA 5blbmb

225 return False 2K BdCdL DdEdM FdGd

226 

227 

228def _scan_editable_direct_url( 2{b~bac|bbccc}bdcec

229 *, 

230 dist_info: Path, 

231 site_packages: Path | None = None, 

232 package_name: str, 

233 package_version: str, 

234 seen_skill_dirs: set[Path], 

235) -> ScanResult: 

236 result = ScanResult() 2p uc@bf m E RbF B v s XbUb0by 1bDcEcq vc[bi n G SbH C w t YbVb2bz 3bFcGcr wc]bl o I TbJ D x u ZbWb4bA 5bHcIc

237 

238 source_root = _read_editable_source_root(dist_info) 2p uc@bf m E RbF B v s XbUb0by 1bDcEcq vc[bi n G SbH C w t YbVb2bz 3bFcGcr wc]bl o I TbJ D x u ZbWb4bA 5bHcIc

239 if source_root is None: 2p uc@bf m E RbF B v s XbUb0by 1bDcEcq vc[bi n G SbH C w t YbVb2bz 3bFcGcr wc]bl o I TbJ D x u ZbWb4bA 5bHcIc

240 return result 2E RbF B v s XbUb0by 1bDcEcG SbH C w t YbVb2bz 3bFcGcI TbJ D x u ZbWb4bA 5bHcIc

241 

242 for skill_md in sorted(source_root.rglob(".agents/skills/*/SKILL.md")): 2p uc@bf m ocpcq vc[bi n qcrcr wc]bl o sctc

243 resolved_skill_md = skill_md.resolve() 2p uc@bf m ocpcq vc[bi n qcrcr wc]bl o sctc

244 if not _is_relative_to(resolved_skill_md, source_root): 2p uc@bf m ocpcq vc[bi n qcrcr wc]bl o sctc

245 continue 2@bheie[bjeke]bleme

246 if site_packages is not None and _is_relative_to( 2p ucf m ocpcq vci n qcrcr wcl o sctc

247 resolved_skill_md, site_packages 

248 ): 

249 continue 2f neoei peqel rese

250 

251 skill_dir = resolved_skill_md.parent 2p ucm ocpcq vcn qcrcr wco sctc

252 if skill_dir in seen_skill_dirs: 2p ucm ocpcq vcn qcrcr wco sctc

253 continue 2ucteuevcvewewcxeye

254 seen_skill_dirs.add(skill_dir) 2p m ocpcq n qcrcr o sctc

255 

256 skill, warning = _load_skill( 2p m ocpcq n qcrcr o sctc

257 skill_dir=skill_dir, 

258 skill_md=resolved_skill_md, 

259 package_name=package_name, 

260 package_version=package_version, 

261 ) 

262 if skill: 2p m ocpcq n qcrcr o sctc

263 result.skills.append(skill) 2m ocpcn qcrco sctc

264 elif warning: 2p 2d3dq 4d5dr 6d7d

265 result.warnings.append(warning) 2p 2d3dq 4d5dr 6d7d

266 

267 return result 2p uc@bf m ocpcq vc[bi n qcrcr wc]bl o sctc

268 

269 

270def _read_editable_source_root(dist_info: Path) -> Path | None: 2{b~bac|bbccc}bdcec

271 direct_url_path = dist_info / "direct_url.json" 2md1cVcMcJcp uc@bf m E RbF B v s XbUb0by 1bDcEcnd2cWcNcKcq vc[bi n G SbH C w t YbVb2bz 3bFcGcod3cXcOcLcr wc]bl o I TbJ D x u ZbWb4bA 5bHcIc

272 try: 2md1cVcMcJcp uc@bf m E RbF B v s XbUb0by 1bDcEcnd2cWcNcKcq vc[bi n G SbH C w t YbVb2bz 3bFcGcod3cXcOcLcr wc]bl o I TbJ D x u ZbWb4bA 5bHcIc

273 data = json.loads(direct_url_path.read_text(encoding="utf-8")) 2md1cVcMcJcp uc@bf m E RbF B v s XbUb0by 1bDcEcnd2cWcNcKcq vc[bi n G SbH C w t YbVb2bz 3bFcGcod3cXcOcLcr wc]bl o I TbJ D x u ZbWb4bA 5bHcIc

274 except (OSError, json.JSONDecodeError): 2E RbF B v s XbUb0by 1bDcEcG SbH C w t YbVb2bz 3bFcGcI TbJ D x u ZbWb4bA 5bHcIc

275 return None 2E RbF B v s XbUb0by 1bDcEcG SbH C w t YbVb2bz 3bFcGcI TbJ D x u ZbWb4bA 5bHcIc

276 

277 if not isinstance(data, dict): 2md1cVcMcJcp uc@bf m ocpcnd2cWcNcKcq vc[bi n qcrcod3cXcOcLcr wc]bl o sctc

278 return None 2mdzeAendBeCeodDeEe

279 

280 dir_info = data.get("dir_info") 21cVcMcJcp uc@bf m ocpc2cWcNcKcq vc[bi n qcrc3cXcOcLcr wc]bl o sctc

281 if not isinstance(dir_info, dict) or dir_info.get("editable") is not True: 21cVcMcJcp uc@bf m ocpc2cWcNcKcq vc[bi n qcrc3cXcOcLcr wc]bl o sctc

282 return None 21cHdId2cJdKd3cLdMd

283 

284 url = data.get("url") 2VcMcJcp uc@bf m ocpcWcNcKcq vc[bi n qcrcXcOcLcr wc]bl o sctc

285 if not isinstance(url, str): 2VcMcJcp uc@bf m ocpcWcNcKcq vc[bi n qcrcXcOcLcr wc]bl o sctc

286 return None 2VcHdIdWcJdKdXcLdMd

287 

288 parsed = urlparse(url) 2McJcp uc@bf m ocpcNcKcq vc[bi n qcrcOcLcr wc]bl o sctc

289 if parsed.scheme != "file": 2McJcp uc@bf m ocpcNcKcq vc[bi n qcrcOcLcr wc]bl o sctc

290 return None 2McHdIdNcJdKdOcLdMd

291 

292 # Windows file URLs use /C:/... paths; url2pathname converts them to C:\... 

293 source_root = Path(url2pathname(parsed.path)).resolve() 2Jcp uc@bf m ocpcKcq vc[bi n qcrcLcr wc]bl o sctc

294 if source_root.is_dir(): 2Jcp uc@bf m ocpcKcq vc[bi n qcrcLcr wc]bl o sctc

295 return source_root 2p uc@bf m ocpcq vc[bi n qcrcr wc]bl o sctc

296 return None 2JcHdIdKcJdKdLcLdMd

297 

298 

299def _is_relative_to(path: Path, parent: Path) -> bool: 2{b~bac|bbccc}bdcec

300 try: 2Ndp uc@bf m ocpcOdq vc[bi n qcrcPdr wc]bl o sctc

301 path.relative_to(parent.resolve()) 2Ndp uc@bf m ocpcOdq vc[bi n qcrcPdr wc]bl o sctc

302 except ValueError: 2Ndp @bm ocpcOdq [bn qcrcPdr ]bo sctc

303 return False 2Ndp @bm ocpcOdq [bn qcrcPdr ]bo sctc

304 return True 2p ucf m ocpcq vci n qcrcr wcl o sctc

305 

306 

307def _load_skill( 2{b~bac|bbccc}bdcec

308 *, 

309 skill_dir: Path, 

310 skill_md: Path, 

311 package_name: str, 

312 package_version: str, 

313) -> tuple[Skill | None, str | None]: 

314 metadata, warning = _parse_skill_frontmatter(skill_md) 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb

315 if warning: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb

316 return None, f"{skill_md}: {warning}" 2RbXbUb0b1b8d9dSbYbVb2b3b!d#dTbZbWb4b5b$d%d

317 

318 name = metadata.get("name", "") 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb

319 description = metadata.get("description", "") 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb

320 validation_error = _validate_skill_metadata( 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb

321 name=name, 

322 description=description, 

323 parent_dir_name=skill_dir.name, 

324 ) 

325 if validation_error: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb

326 return None, f"{skill_md}: {validation_error}" 2p ;bE F B v s y vdwdq =bG H C w t z xdydr ?bI J D x u A zdAd

327 

328 return ( 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 ^b.bvbf K wbm 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? _b/bFbi L Gbn @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkb`b:bPbl M Qbo lbmb

329 Skill( 

330 name=name, 

331 description=description, 

332 path=skill_md, 

333 package_name=package_name, 

334 package_version=package_version, 

335 skill_dir=skill_dir, 

336 ), 

337 None, 

338 ) 

339 

340 

341def _parse_skill_frontmatter(skill_md: Path) -> tuple[dict[str, str], str | None]: 2{b~bac|bbccc}bdcec

342 try: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb

343 text = skill_md.read_text(encoding="utf-8") 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s XbUb0by 1b6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t YbVb2bz 3b@ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u ZbWb4bA 5blbmb

344 except (OSError, UnicodeDecodeError) as e: 21bQdRd3bSdTd5bUdVd

345 return {}, f"could not read SKILL.md ({e})" 21bQdRd3bSdTd5bUdVd

346 

347 if not text.startswith("---"): 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s XbUb0by 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t YbVb2bz @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u ZbWb4bA lbmb

348 return {}, "missing YAML frontmatter" 20b8d9d2b!d#d4b$d%d

349 

350 end = text.find("\n---", 3) 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s XbUby 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t YbVbz @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u ZbWbA lbmb

351 if end == -1: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s XbUby 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t YbVbz @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u ZbWbA lbmb

352 return {}, "unterminated YAML frontmatter" 2Xb4c5cYb6c7cZb8c9c

353 

354 frontmatter = text[3:end] 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s Uby 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t Vbz @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u WbA lbmb

355 try: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s Uby 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t Vbz @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u WbA lbmb

356 parsed = yaml.safe_load(frontmatter) 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s Uby 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t Vbz @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u WbA lbmb

357 except yaml.YAMLError as e: 2Ub4c5cVb6c7cWb8c9c

358 return {}, f"invalid YAML frontmatter ({e})" 2Ub4c5cVb6c7cWb8c9c

359 

360 if parsed is None: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u A lbmb

361 parsed = {} 2E 'd(dG )d*dI +d,d

362 

363 if not isinstance(parsed, dict): 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E RbF B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G SbH C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I TbJ D x u A lbmb

364 return {}, "YAML frontmatter must be a mapping" 2Rb'd(dSb)d*dTb+d,d

365 

366 metadata: dict[str, str] = {} 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb

367 for key in ("name", "description"): 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb

368 value = parsed.get(key) 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb

369 if isinstance(value, str): 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb

370 metadata[key] = value.strip() 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo J D x u A lbmb

371 

372 return metadata, None 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb

373 

374 

375def _validate_skill_metadata( 2{b~bac|bbccc}bdcec

376 *, 

377 name: str, 

378 description: str, 

379 parent_dir_name: str, 

380) -> str | None: 

381 if not name: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm E F B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn G H C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo I J D x u A lbmb

382 return "missing required 'name' field" 2E F 4c5cG H 6c7cI J 8c9c

383 if not _SKILL_NAME_RE.fullmatch(name) or "--" in name: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm B v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn C w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo D x u A lbmb

384 return "invalid 'name' field; use lowercase letters, numbers, and hyphens only" 2B 4c5cC 6c7cD 8c9c

385 if name != parent_dir_name: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 p ^b.b;bvbf K wbm v s y 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? q _b/b=bFbi L Gbn w t z @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkbr `b:b?bPbl M Qbo x u A lbmb

386 return f"'name' field must match parent directory name ({parent_dir_name})" 2p ;by vdwdq =bz xdydr ?bA zdAd

387 if not description: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 ^b.bvbf K wbm v s 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? _b/bFbi L Gbn w t @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkb`b:bPbl M Qbo x u lbmb

388 return "missing required 'description' field" 2v 4c5cw 6c7cx 8c9c

389 if len(description) > 1024: 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 ^b.bvbf K wbm s 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? _b/bFbi L Gbn t @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkb`b:bPbl M Qbo u lbmb

390 return "'description' field must be at most 1024 characters" 2s QdRdt SdTdu UdVd

391 return None 2N d e O P Q R nbobS T U pbqbV W X Y 6b7b8b9b!brbsbtbubZ a 0 1 2 3 4 5 ^b.bvbf K wbm 6 7 8 g h 9 ! # $ xbyb% ' ( zbAb) * + , #b$b%b'b(bBbCbDbEb- b . / : ; = ? _b/bFbi L Gbn @ [ ] j k ^ _ ` { HbIb| } ~ JbKbabbbcbdb)b*b+b,b-bLbMbNbObebc fbgbhbibjbkb`b:bPbl M Qbo lbmb

392 

393 

394def _normalize_package_name(name: str) -> str: 2{b~bac|bbccc}bdcec

395 """Normalize a Python package name (PEP 503).""" 

396 return re.sub(r"[-_.]+", "-", name).lower() 2N d e O P Q R S T U V W X Y 6b7b8b9b!bZ a 0 1 2 3 4 5 FeGeHeIeJe6 7 8 g h 9 ! # $ % ' ( ) * + , #b$b%b'b(b- b . / : ; = ? KeLeMeNeOe@ [ ] j k ^ _ ` { | } ~ abbbcbdb)b*b+b,b-bebc fbgbhbibjbkbPeQeReSeTelbmb