@@ -13,23 +13,23 @@ import (
1313 "github.com/stretchr/testify/require"
1414)
1515
16- // pullRequestsSkillURI / inboxTriageSkillURI are the canonical URIs of the
17- // bundled skills, derived from skills.Bundled so tests never drift from the
18- // single source of truth.
16+ // reviewPRSkillURI / handleNotificationsSkillURI are the canonical URIs of the
17+ // two bundled skills, derived from skills.Bundled so tests never drift from
18+ // the single source of truth.
1919var (
20- pullRequestsSkillURI = skills.Bundled {Name : "pull-requests " }.URI ()
21- inboxTriageSkillURI = skills.Bundled {Name : "inbox-triage " }.URI ()
20+ reviewPRSkillURI = skills.Bundled {Name : "review-pr " }.URI ()
21+ handleNotificationsSkillURI = skills.Bundled {Name : "handle-notifications " }.URI ()
2222)
2323
24- // Test_PullRequestsSkill_EmbeddedContent verifies the SEP structural requirement
24+ // Test_ReviewPRSkill_EmbeddedContent verifies the SEP structural requirement
2525// that the frontmatter `name` field matches the final segment of the skill-path
2626// in the URI, and that the substantive tool-sequence content is preserved.
27- func Test_PullRequestsSkill_EmbeddedContent (t * testing.T ) {
28- require .NotEmpty (t , skills .PullRequestsSKILL , "SKILL.md must be embedded" )
27+ func Test_ReviewPRSkill_EmbeddedContent (t * testing.T ) {
28+ require .NotEmpty (t , skills .ReviewPRSKILL , "SKILL.md must be embedded" )
2929
3030 // Normalize line endings so the test is robust to git's autocrlf behavior
3131 // on Windows checkouts — the embedded SKILL.md may arrive as CRLF.
32- md := strings .ReplaceAll (skills .PullRequestsSKILL , "\r \n " , "\n " )
32+ md := strings .ReplaceAll (skills .ReviewPRSKILL , "\r \n " , "\n " )
3333 require .True (t , strings .HasPrefix (md , "---\n " ), "SKILL.md must begin with YAML frontmatter" )
3434
3535 end := strings .Index (md [4 :], "\n ---\n " )
@@ -44,7 +44,7 @@ func Test_PullRequestsSkill_EmbeddedContent(t *testing.T) {
4444 }
4545 }
4646 require .NotEmpty (t , frontmatterName , "SKILL.md frontmatter must declare `name`" )
47- assert .Equal (t , "pull-requests " , frontmatterName , "frontmatter name must match final skill-path segment in %s" , pullRequestsSkillURI )
47+ assert .Equal (t , "review-pr " , frontmatterName , "frontmatter name must match final skill-path segment in %s" , reviewPRSkillURI )
4848
4949 body := md [4 + end + 5 :]
5050 assert .Contains (t , body , "## Workflow" , "skill body must carry the workflow section" )
@@ -53,13 +53,13 @@ func Test_PullRequestsSkill_EmbeddedContent(t *testing.T) {
5353 assert .Contains (t , body , "submit_pending" , "the distinctive tool method must be present" )
5454}
5555
56- // Test_InboxTriageSkill_EmbeddedContent verifies the SEP structural
57- // requirements for the inbox-triage skill and that its substantive tool
58- // references are preserved.
59- func Test_InboxTriageSkill_EmbeddedContent (t * testing.T ) {
60- require .NotEmpty (t , skills .InboxTriageSKILL , "SKILL.md must be embedded" )
56+ // Test_HandleNotificationsSkill_EmbeddedContent verifies the SEP structural
57+ // requirements for the handle-notifications skill and that its substantive
58+ // tool references are preserved.
59+ func Test_HandleNotificationsSkill_EmbeddedContent (t * testing.T ) {
60+ require .NotEmpty (t , skills .HandleNotificationsSKILL , "SKILL.md must be embedded" )
6161
62- md := strings .ReplaceAll (skills .InboxTriageSKILL , "\r \n " , "\n " )
62+ md := strings .ReplaceAll (skills .HandleNotificationsSKILL , "\r \n " , "\n " )
6363 require .True (t , strings .HasPrefix (md , "---\n " ), "SKILL.md must begin with YAML frontmatter" )
6464
6565 end := strings .Index (md [4 :], "\n ---\n " )
@@ -74,98 +74,40 @@ func Test_InboxTriageSkill_EmbeddedContent(t *testing.T) {
7474 }
7575 }
7676 require .NotEmpty (t , frontmatterName , "SKILL.md frontmatter must declare `name`" )
77- assert .Equal (t , "inbox-triage " , frontmatterName , "frontmatter name must match final skill-path segment in %s" , inboxTriageSkillURI )
77+ assert .Equal (t , "handle-notifications " , frontmatterName , "frontmatter name must match final skill-path segment in %s" , handleNotificationsSkillURI )
7878
7979 body := md [4 + end + 5 :]
8080 assert .Contains (t , body , "## Workflow" )
8181 assert .Contains (t , body , "list_notifications" , "triage workflow must reference list_notifications" )
8282 assert .Contains (t , body , "dismiss_notification" , "triage workflow must reference dismiss_notification" )
8383}
8484
85- // Test_BundledSkills_Registration verifies that skill resources are
86- // registered when the backing toolset is enabled, and omitted when it is not.
87- func Test_BundledSkills_Registration (t * testing.T ) {
85+ // Test_BundledSkills_RegisterRegardlessOfToolset verifies that bundled skills
86+ // load uniformly — they're always-on and don't depend on which toolsets the
87+ // inventory has enabled. Per-skill toolset gating remains available via the
88+ // Registry's Enabled closure but no shipped skill currently uses it.
89+ func Test_BundledSkills_RegisterRegardlessOfToolset (t * testing.T ) {
8890 ctx := context .Background ()
8991
90- t .Run ("registers when pull_requests toolset enabled" , func (t * testing.T ) {
91- inv , err := NewInventory (translations .NullTranslationHelper ).
92- WithToolsets ([]string {string (ToolsetMetadataPullRequests .ID )}).
93- Build ()
94- require .NoError (t , err )
95-
96- srv := mcp .NewServer (& mcp.Implementation {Name : "test" }, & mcp.ServerOptions {
97- Capabilities : & mcp.ServerCapabilities {Resources : & mcp.ResourceCapabilities {}},
98- })
99- RegisterBundledSkills (srv , inv )
100-
101- mimes := map [string ]string {}
102- for _ , r := range listResources (t , ctx , srv ) {
103- mimes [r .URI ] = r .MIMEType
104- }
105- assert .Equal (t , "text/markdown" , mimes [pullRequestsSkillURI ])
106- assert .Equal (t , "application/json" , mimes [skills .IndexURI ])
107- })
108-
109- t .Run ("omits when pull_requests toolset disabled" , func (t * testing.T ) {
110- inv , err := NewInventory (translations .NullTranslationHelper ).
111- WithToolsets ([]string {string (ToolsetMetadataContext .ID )}).
112- Build ()
113- require .NoError (t , err )
114-
115- srv := mcp .NewServer (& mcp.Implementation {Name : "test" }, & mcp.ServerOptions {
116- Capabilities : & mcp.ServerCapabilities {Resources : & mcp.ResourceCapabilities {}},
117- })
118- RegisterBundledSkills (srv , inv )
119-
120- for _ , r := range listResources (t , ctx , srv ) {
121- assert .NotEqual (t , pullRequestsSkillURI , r .URI )
122- assert .NotEqual (t , inboxTriageSkillURI , r .URI )
123- assert .NotEqual (t , skills .IndexURI , r .URI )
124- }
125- })
126-
127- t .Run ("registers inbox-triage when notifications toolset enabled" , func (t * testing.T ) {
128- inv , err := NewInventory (translations .NullTranslationHelper ).
129- WithToolsets ([]string {string (ToolsetMetadataNotifications .ID )}).
130- Build ()
131- require .NoError (t , err )
132-
133- srv := mcp .NewServer (& mcp.Implementation {Name : "test" }, & mcp.ServerOptions {
134- Capabilities : & mcp.ServerCapabilities {Resources : & mcp.ResourceCapabilities {}},
135- })
136- RegisterBundledSkills (srv , inv )
92+ // Pick a minimal toolset (context only) — doesn't enable pull_requests
93+ // or notifications, the toolsets the renamed skills used to be gated on.
94+ inv , err := NewInventory (translations .NullTranslationHelper ).
95+ WithToolsets ([]string {string (ToolsetMetadataContext .ID )}).
96+ Build ()
97+ require .NoError (t , err )
13798
138- uris := map [string ]string {}
139- for _ , r := range listResources (t , ctx , srv ) {
140- uris [r .URI ] = r .MIMEType
141- }
142- assert .Equal (t , "text/markdown" , uris [inboxTriageSkillURI ])
143- assert .NotContains (t , uris , pullRequestsSkillURI , "only notifications enabled — pull-requests should not be registered" )
144- assert .Equal (t , "application/json" , uris [skills .IndexURI ])
99+ srv := mcp .NewServer (& mcp.Implementation {Name : "test" }, & mcp.ServerOptions {
100+ Capabilities : & mcp.ServerCapabilities {Resources : & mcp.ResourceCapabilities {}},
145101 })
102+ RegisterBundledSkills (srv , inv )
146103
147- t .Run ("registers both when both toolsets enabled" , func (t * testing.T ) {
148- inv , err := NewInventory (translations .NullTranslationHelper ).
149- WithToolsets ([]string {
150- string (ToolsetMetadataPullRequests .ID ),
151- string (ToolsetMetadataNotifications .ID ),
152- }).
153- Build ()
154- require .NoError (t , err )
155-
156- srv := mcp .NewServer (& mcp.Implementation {Name : "test" }, & mcp.ServerOptions {
157- Capabilities : & mcp.ServerCapabilities {Resources : & mcp.ResourceCapabilities {}},
158- })
159- RegisterBundledSkills (srv , inv )
160-
161- uris := map [string ]struct {}{}
162- for _ , r := range listResources (t , ctx , srv ) {
163- uris [r .URI ] = struct {}{}
164- }
165- assert .Contains (t , uris , pullRequestsSkillURI )
166- assert .Contains (t , uris , inboxTriageSkillURI )
167- assert .Contains (t , uris , skills .IndexURI )
168- })
104+ uris := map [string ]string {}
105+ for _ , r := range listResources (t , ctx , srv ) {
106+ uris [r .URI ] = r .MIMEType
107+ }
108+ assert .Equal (t , "text/markdown" , uris [reviewPRSkillURI ], "review-pr registers regardless of toolset" )
109+ assert .Equal (t , "text/markdown" , uris [handleNotificationsSkillURI ], "handle-notifications registers regardless of toolset" )
110+ assert .Equal (t , "application/json" , uris [skills .IndexURI ])
169111}
170112
171113// Test_BundledSkills_ReadContent verifies that reading the skill resource
@@ -174,7 +116,7 @@ func Test_BundledSkills_Registration(t *testing.T) {
174116func Test_BundledSkills_ReadContent (t * testing.T ) {
175117 ctx := context .Background ()
176118 inv , err := NewInventory (translations .NullTranslationHelper ).
177- WithToolsets ([]string {string (ToolsetMetadataPullRequests .ID )}).
119+ WithToolsets ([]string {string (ToolsetMetadataContext .ID )}).
178120 Build ()
179121 require .NoError (t , err )
180122
@@ -186,11 +128,11 @@ func Test_BundledSkills_ReadContent(t *testing.T) {
186128 session := connectClient (t , ctx , srv )
187129
188130 t .Run ("SKILL.md content" , func (t * testing.T ) {
189- res , err := session .ReadResource (ctx , & mcp.ReadResourceParams {URI : pullRequestsSkillURI })
131+ res , err := session .ReadResource (ctx , & mcp.ReadResourceParams {URI : reviewPRSkillURI })
190132 require .NoError (t , err )
191133 require .Len (t , res .Contents , 1 )
192134 assert .Equal (t , "text/markdown" , res .Contents [0 ].MIMEType )
193- assert .Equal (t , skills .PullRequestsSKILL , res .Contents [0 ].Text )
135+ assert .Equal (t , skills .ReviewPRSKILL , res .Contents [0 ].Text )
194136 })
195137
196138 t .Run ("index.json matches SEP discovery schema" , func (t * testing.T ) {
@@ -202,23 +144,31 @@ func Test_BundledSkills_ReadContent(t *testing.T) {
202144 var idx skills.IndexDoc
203145 require .NoError (t , json .Unmarshal ([]byte (res .Contents [0 ].Text ), & idx ))
204146 assert .Equal (t , skills .IndexSchema , idx .Schema )
205- require .Len (t , idx .Skills , 1 )
206- assert .Equal (t , "pull-requests" , idx .Skills [0 ].Name )
207- assert .Equal (t , "skill-md" , idx .Skills [0 ].Type )
208- assert .Equal (t , pullRequestsSkillURI , idx .Skills [0 ].URL )
209- assert .NotEmpty (t , idx .Skills [0 ].Description )
147+
148+ // Index size must equal the number of currently-enabled bundled skills.
149+ assert .Len (t , idx .Skills , len (bundledSkills (inv ).Enabled ()))
150+
151+ // The review-pr skill must be present.
152+ var found * skills.IndexEntry
153+ for i := range idx .Skills {
154+ if idx .Skills [i ].Name == "review-pr" {
155+ found = & idx .Skills [i ]
156+ break
157+ }
158+ }
159+ require .NotNil (t , found , "review-pr must appear in the index" )
160+ assert .Equal (t , "skill-md" , found .Type )
161+ assert .Equal (t , reviewPRSkillURI , found .URL )
162+ assert .NotEmpty (t , found .Description )
210163 })
211164}
212165
213- // Test_BundledSkills_Index_MultipleSkills verifies that all enabled skills
166+ // Test_BundledSkills_Index_MultipleSkills verifies that all bundled skills
214167// appear in the discovery index, not just the first one.
215168func Test_BundledSkills_Index_MultipleSkills (t * testing.T ) {
216169 ctx := context .Background ()
217170 inv , err := NewInventory (translations .NullTranslationHelper ).
218- WithToolsets ([]string {
219- string (ToolsetMetadataPullRequests .ID ),
220- string (ToolsetMetadataNotifications .ID ),
221- }).
171+ WithToolsets ([]string {string (ToolsetMetadataContext .ID )}).
222172 Build ()
223173 require .NoError (t , err )
224174
@@ -237,29 +187,17 @@ func Test_BundledSkills_Index_MultipleSkills(t *testing.T) {
237187 for _ , s := range idx .Skills {
238188 names [s .Name ] = s .URL
239189 }
240- assert .Equal (t , pullRequestsSkillURI , names ["pull-requests " ])
241- assert .Equal (t , inboxTriageSkillURI , names ["inbox-triage " ])
190+ assert .Equal (t , reviewPRSkillURI , names ["review-pr " ])
191+ assert .Equal (t , handleNotificationsSkillURI , names ["handle-notifications " ])
242192}
243193
244194// Test_DeclareSkillsExtensionIfEnabled verifies that the skills-over-MCP
245- // extension (SEP-2133) is declared in ServerOptions.Capabilities when the
246- // pull_requests toolset is enabled, and is absent when it is not.
195+ // extension (SEP-2133) is declared in ServerOptions.Capabilities whenever the
196+ // server has any bundled skill or template entry to publish, and that
197+ // declaration is additive (doesn't clobber other extensions).
247198func Test_DeclareSkillsExtensionIfEnabled (t * testing.T ) {
248- t .Run ("declares when pull_requests enabled" , func (t * testing.T ) {
249- inv , err := NewInventory (translations .NullTranslationHelper ).
250- WithToolsets ([]string {string (ToolsetMetadataPullRequests .ID )}).
251- Build ()
252- require .NoError (t , err )
253-
254- opts := & mcp.ServerOptions {}
255- DeclareSkillsExtensionIfEnabled (opts , inv )
256-
257- require .NotNil (t , opts .Capabilities )
258- _ , ok := opts .Capabilities .Extensions [skills .ExtensionKey ]
259- assert .True (t , ok , "skills extension must be declared" )
260- })
261-
262- t .Run ("does not declare when pull_requests disabled" , func (t * testing.T ) {
199+ t .Run ("declares for any non-empty registry" , func (t * testing.T ) {
200+ // All bundled skills are always-on, so any inventory triggers the declaration.
263201 inv , err := NewInventory (translations .NullTranslationHelper ).
264202 WithToolsets ([]string {string (ToolsetMetadataContext .ID )}).
265203 Build ()
@@ -268,29 +206,14 @@ func Test_DeclareSkillsExtensionIfEnabled(t *testing.T) {
268206 opts := & mcp.ServerOptions {}
269207 DeclareSkillsExtensionIfEnabled (opts , inv )
270208
271- if opts .Capabilities != nil {
272- _ , ok := opts .Capabilities .Extensions [skills .ExtensionKey ]
273- assert .False (t , ok , "skills extension must NOT be declared when no skills will be registered" )
274- }
275- })
276-
277- t .Run ("declares when notifications enabled (any skill triggers declaration)" , func (t * testing.T ) {
278- inv , err := NewInventory (translations .NullTranslationHelper ).
279- WithToolsets ([]string {string (ToolsetMetadataNotifications .ID )}).
280- Build ()
281- require .NoError (t , err )
282-
283- opts := & mcp.ServerOptions {}
284- DeclareSkillsExtensionIfEnabled (opts , inv )
285-
286209 require .NotNil (t , opts .Capabilities )
287210 _ , ok := opts .Capabilities .Extensions [skills .ExtensionKey ]
288- assert .True (t , ok , "skills extension must be declared when any bundled skill is enabled " )
211+ assert .True (t , ok , "always-on skills register, so the extension must be declared" )
289212 })
290213
291214 t .Run ("preserves other extensions already declared" , func (t * testing.T ) {
292215 inv , err := NewInventory (translations .NullTranslationHelper ).
293- WithToolsets ([]string {string (ToolsetMetadataPullRequests .ID )}).
216+ WithToolsets ([]string {string (ToolsetMetadataContext .ID )}).
294217 Build ()
295218 require .NoError (t , err )
296219
@@ -306,6 +229,17 @@ func Test_DeclareSkillsExtensionIfEnabled(t *testing.T) {
306229 assert .True (t , hasSkills )
307230 assert .True (t , hasOther , "existing extensions must not be overwritten" )
308231 })
232+
233+ t .Run ("does not declare for an empty registry" , func (t * testing.T ) {
234+ // Tests the Registry's failure path directly — bypasses bundledSkills()
235+ // since no shipped configuration produces an empty registry.
236+ opts := & mcp.ServerOptions {}
237+ skills .New ().DeclareCapability (opts )
238+ if opts .Capabilities != nil {
239+ _ , ok := opts .Capabilities .Extensions [skills .ExtensionKey ]
240+ assert .False (t , ok , "empty registry must not declare the extension" )
241+ }
242+ })
309243}
310244
311245// listResources enumerates resources/list via an in-memory client session.
0 commit comments