@@ -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 skills with the most distinctive content. Derived from skills.Bundled so
18+ // tests never drift from 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,103 +74,41 @@ 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 (covered by registry-level unit tests, not here)
89+ // but no shipped skill currently uses it.
90+ func Test_BundledSkills_RegisterRegardlessOfToolset (t * testing.T ) {
8891 ctx := context .Background ()
8992
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 toolset-gated skills when their toolsets are 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- uris := map [string ]struct {}{}
121- for _ , r := range listResources (t , ctx , srv ) {
122- uris [r .URI ] = struct {}{}
123- }
124- // Toolset-gated skills must be absent.
125- assert .NotContains (t , uris , pullRequestsSkillURI , "pull-requests is gated on pull_requests toolset" )
126- assert .NotContains (t , uris , inboxTriageSkillURI , "inbox-triage is gated on notifications toolset" )
127- // Always-on skills (e.g. get-context) and the index remain registered.
128- assert .Contains (t , uris , skills.Bundled {Name : "get-context" }.URI (), "always-on skill must still register" )
129- assert .Contains (t , uris , skills .IndexURI , "index is published whenever any skill is enabled" )
130- })
131-
132- t .Run ("registers inbox-triage when notifications toolset enabled" , func (t * testing.T ) {
133- inv , err := NewInventory (translations .NullTranslationHelper ).
134- WithToolsets ([]string {string (ToolsetMetadataNotifications .ID )}).
135- Build ()
136- require .NoError (t , err )
137-
138- srv := mcp .NewServer (& mcp.Implementation {Name : "test" }, & mcp.ServerOptions {
139- Capabilities : & mcp.ServerCapabilities {Resources : & mcp.ResourceCapabilities {}},
140- })
141- RegisterBundledSkills (srv , inv )
93+ // Pick a minimal toolset (context only) — doesn't enable pull_requests
94+ // or notifications, the toolsets the renamed skills used to be gated on.
95+ inv , err := NewInventory (translations .NullTranslationHelper ).
96+ WithToolsets ([]string {string (ToolsetMetadataContext .ID )}).
97+ Build ()
98+ require .NoError (t , err )
14299
143- uris := map [string ]string {}
144- for _ , r := range listResources (t , ctx , srv ) {
145- uris [r .URI ] = r .MIMEType
146- }
147- assert .Equal (t , "text/markdown" , uris [inboxTriageSkillURI ])
148- assert .NotContains (t , uris , pullRequestsSkillURI , "only notifications enabled — pull-requests should not be registered" )
149- assert .Equal (t , "application/json" , uris [skills .IndexURI ])
100+ srv := mcp .NewServer (& mcp.Implementation {Name : "test" }, & mcp.ServerOptions {
101+ Capabilities : & mcp.ServerCapabilities {Resources : & mcp.ResourceCapabilities {}},
150102 })
103+ RegisterBundledSkills (srv , inv )
151104
152- t .Run ("registers both when both toolsets enabled" , func (t * testing.T ) {
153- inv , err := NewInventory (translations .NullTranslationHelper ).
154- WithToolsets ([]string {
155- string (ToolsetMetadataPullRequests .ID ),
156- string (ToolsetMetadataNotifications .ID ),
157- }).
158- Build ()
159- require .NoError (t , err )
160-
161- srv := mcp .NewServer (& mcp.Implementation {Name : "test" }, & mcp.ServerOptions {
162- Capabilities : & mcp.ServerCapabilities {Resources : & mcp.ResourceCapabilities {}},
163- })
164- RegisterBundledSkills (srv , inv )
165-
166- uris := map [string ]struct {}{}
167- for _ , r := range listResources (t , ctx , srv ) {
168- uris [r .URI ] = struct {}{}
169- }
170- assert .Contains (t , uris , pullRequestsSkillURI )
171- assert .Contains (t , uris , inboxTriageSkillURI )
172- assert .Contains (t , uris , skills .IndexURI )
173- })
105+ uris := map [string ]string {}
106+ for _ , r := range listResources (t , ctx , srv ) {
107+ uris [r .URI ] = r .MIMEType
108+ }
109+ assert .Equal (t , "text/markdown" , uris [reviewPRSkillURI ], "review-pr registers regardless of toolset" )
110+ assert .Equal (t , "text/markdown" , uris [handleNotificationsSkillURI ], "handle-notifications registers regardless of toolset" )
111+ assert .Equal (t , "application/json" , uris [skills .IndexURI ])
174112}
175113
176114// Test_BundledSkills_ReadContent verifies that reading the skill resource
@@ -179,7 +117,7 @@ func Test_BundledSkills_Registration(t *testing.T) {
179117func Test_BundledSkills_ReadContent (t * testing.T ) {
180118 ctx := context .Background ()
181119 inv , err := NewInventory (translations .NullTranslationHelper ).
182- WithToolsets ([]string {string ( ToolsetMetadataPullRequests . ID ) }).
120+ WithToolsets ([]string {"all" }).
183121 Build ()
184122 require .NoError (t , err )
185123
@@ -191,11 +129,11 @@ func Test_BundledSkills_ReadContent(t *testing.T) {
191129 session := connectClient (t , ctx , srv )
192130
193131 t .Run ("SKILL.md content" , func (t * testing.T ) {
194- res , err := session .ReadResource (ctx , & mcp.ReadResourceParams {URI : pullRequestsSkillURI })
132+ res , err := session .ReadResource (ctx , & mcp.ReadResourceParams {URI : reviewPRSkillURI })
195133 require .NoError (t , err )
196134 require .Len (t , res .Contents , 1 )
197135 assert .Equal (t , "text/markdown" , res .Contents [0 ].MIMEType )
198- assert .Equal (t , skills .PullRequestsSKILL , res .Contents [0 ].Text )
136+ assert .Equal (t , skills .ReviewPRSKILL , res .Contents [0 ].Text )
199137 })
200138
201139 t .Run ("index.json matches SEP discovery schema" , func (t * testing.T ) {
@@ -208,33 +146,31 @@ func Test_BundledSkills_ReadContent(t *testing.T) {
208146 require .NoError (t , json .Unmarshal ([]byte (res .Contents [0 ].Text ), & idx ))
209147 assert .Equal (t , skills .IndexSchema , idx .Schema )
210148
211- // Index size must equal the number of currently-enabled bundled skills.
212- assert .Len (t , idx .Skills , len (bundledSkills (inv ).Enabled ()))
149+ // Index size must equal the number of currently-enabled bundled skills + templates.
150+ registry := bundledSkills (inv )
151+ assert .Len (t , idx .Skills , len (registry .Enabled ())+ len (registry .EnabledTemplates ()))
213152
214- // The pull-requests skill (toolset-gated, currently enabled) must be present.
153+ // The review-pr skill must be present.
215154 var found * skills.IndexEntry
216155 for i := range idx .Skills {
217- if idx .Skills [i ].Name == "pull-requests " {
156+ if idx .Skills [i ].Name == "review-pr " {
218157 found = & idx .Skills [i ]
219158 break
220159 }
221160 }
222- require .NotNil (t , found , "pull-requests must appear in the index" )
161+ require .NotNil (t , found , "review-pr must appear in the index" )
223162 assert .Equal (t , "skill-md" , found .Type )
224- assert .Equal (t , pullRequestsSkillURI , found .URL )
163+ assert .Equal (t , reviewPRSkillURI , found .URL )
225164 assert .NotEmpty (t , found .Description )
226165 })
227166}
228167
229- // Test_BundledSkills_Index_MultipleSkills verifies that all enabled skills
168+ // Test_BundledSkills_Index_MultipleSkills verifies that all bundled skills
230169// appear in the discovery index, not just the first one.
231170func Test_BundledSkills_Index_MultipleSkills (t * testing.T ) {
232171 ctx := context .Background ()
233172 inv , err := NewInventory (translations .NullTranslationHelper ).
234- WithToolsets ([]string {
235- string (ToolsetMetadataPullRequests .ID ),
236- string (ToolsetMetadataNotifications .ID ),
237- }).
173+ WithToolsets ([]string {string (ToolsetMetadataContext .ID )}).
238174 Build ()
239175 require .NoError (t , err )
240176
@@ -253,31 +189,17 @@ func Test_BundledSkills_Index_MultipleSkills(t *testing.T) {
253189 for _ , s := range idx .Skills {
254190 names [s .Name ] = s .URL
255191 }
256- assert .Equal (t , pullRequestsSkillURI , names ["pull-requests " ])
257- assert .Equal (t , inboxTriageSkillURI , names ["inbox-triage " ])
192+ assert .Equal (t , reviewPRSkillURI , names ["review-pr " ])
193+ assert .Equal (t , handleNotificationsSkillURI , names ["handle-notifications " ])
258194}
259195
260196// Test_DeclareSkillsExtensionIfEnabled verifies that the skills-over-MCP
261- // extension (SEP-2133) is declared in ServerOptions.Capabilities when the
262- // pull_requests toolset is enabled, and is absent when it is not.
197+ // extension (SEP-2133) is declared in ServerOptions.Capabilities whenever the
198+ // server has any bundled skill or template entry to publish, and that
199+ // declaration is additive (doesn't clobber other extensions).
263200func Test_DeclareSkillsExtensionIfEnabled (t * testing.T ) {
264- t .Run ("declares when pull_requests enabled" , func (t * testing.T ) {
265- inv , err := NewInventory (translations .NullTranslationHelper ).
266- WithToolsets ([]string {string (ToolsetMetadataPullRequests .ID )}).
267- Build ()
268- require .NoError (t , err )
269-
270- opts := & mcp.ServerOptions {}
271- DeclareSkillsExtensionIfEnabled (opts , inv )
272-
273- require .NotNil (t , opts .Capabilities )
274- _ , ok := opts .Capabilities .Extensions [skills .ExtensionKey ]
275- assert .True (t , ok , "skills extension must be declared" )
276- })
277-
278- t .Run ("declares even when no toolset-gated skills enabled (always-on skills exist)" , func (t * testing.T ) {
279- // Even with only the context toolset, always-on bundled skills (e.g. get-context)
280- // register, so the extension capability MUST still be declared.
201+ t .Run ("declares for any non-empty registry" , func (t * testing.T ) {
202+ // All bundled skills are always-on, so any inventory triggers the declaration.
281203 inv , err := NewInventory (translations .NullTranslationHelper ).
282204 WithToolsets ([]string {string (ToolsetMetadataContext .ID )}).
283205 Build ()
@@ -291,23 +213,9 @@ func Test_DeclareSkillsExtensionIfEnabled(t *testing.T) {
291213 assert .True (t , ok , "always-on skills register, so the extension must be declared" )
292214 })
293215
294- t .Run ("declares when notifications enabled (any skill triggers declaration)" , func (t * testing.T ) {
295- inv , err := NewInventory (translations .NullTranslationHelper ).
296- WithToolsets ([]string {string (ToolsetMetadataNotifications .ID )}).
297- Build ()
298- require .NoError (t , err )
299-
300- opts := & mcp.ServerOptions {}
301- DeclareSkillsExtensionIfEnabled (opts , inv )
302-
303- require .NotNil (t , opts .Capabilities )
304- _ , ok := opts .Capabilities .Extensions [skills .ExtensionKey ]
305- assert .True (t , ok , "skills extension must be declared when any bundled skill is enabled" )
306- })
307-
308216 t .Run ("preserves other extensions already declared" , func (t * testing.T ) {
309217 inv , err := NewInventory (translations .NullTranslationHelper ).
310- WithToolsets ([]string {string (ToolsetMetadataPullRequests .ID )}).
218+ WithToolsets ([]string {string (ToolsetMetadataContext .ID )}).
311219 Build ()
312220 require .NoError (t , err )
313221
@@ -323,6 +231,17 @@ func Test_DeclareSkillsExtensionIfEnabled(t *testing.T) {
323231 assert .True (t , hasSkills )
324232 assert .True (t , hasOther , "existing extensions must not be overwritten" )
325233 })
234+
235+ t .Run ("does not declare for an empty registry" , func (t * testing.T ) {
236+ // Tests the Registry's failure path directly — bypasses bundledSkills()
237+ // since no shipped configuration produces an empty registry.
238+ opts := & mcp.ServerOptions {}
239+ skills .New ().DeclareCapability (opts )
240+ if opts .Capabilities != nil {
241+ _ , ok := opts .Capabilities .Extensions [skills .ExtensionKey ]
242+ assert .False (t , ok , "empty registry must not declare the extension" )
243+ }
244+ })
326245}
327246
328247// Test_BundledSkills_AllRegistered_WhenAllToolsetsEnabled verifies that with the
0 commit comments