@@ -145,6 +145,115 @@ Use this skill.
145145 }
146146 } )
147147
148+ // altimate_change start — e2e tests for follow-up suggestions in skill tool output
149+ test ( "execute includes follow-up suggestions before skill_content for mapped skills" , async ( ) => {
150+ await using tmp = await tmpdir ( {
151+ git : true ,
152+ init : async ( dir ) => {
153+ const skillDir = path . join ( dir , ".opencode" , "skill" , "dbt-develop" )
154+ await Bun . write (
155+ path . join ( skillDir , "SKILL.md" ) ,
156+ `---
157+ name: dbt-develop
158+ description: Create dbt models.
159+ ---
160+
161+ # dbt Model Development
162+
163+ Build models with dbt.
164+ ` ,
165+ )
166+ } ,
167+ } )
168+
169+ const home = process . env . OPENCODE_TEST_HOME
170+ process . env . OPENCODE_TEST_HOME = tmp . path
171+
172+ try {
173+ await Instance . provide ( {
174+ directory : tmp . path ,
175+ fn : async ( ) => {
176+ const tool = await SkillTool . init ( )
177+ const ctx : Tool . Context = {
178+ ...baseCtx ,
179+ ask : async ( ) => { } ,
180+ }
181+
182+ const result = await tool . execute ( { name : "dbt-develop" } , ctx )
183+
184+ // Follow-ups present
185+ expect ( result . output ) . toContain ( "## What's Next?" )
186+ expect ( result . output ) . toContain ( "dbt-test" )
187+ expect ( result . output ) . toContain ( "dbt-docs" )
188+ expect ( result . output ) . toContain ( "dbt-analyze" )
189+ expect ( result . output ) . toContain ( "/discover" )
190+
191+ // Follow-ups appear BEFORE skill_content (truncation-safe)
192+ const followupsIdx = result . output . indexOf ( "## What's Next?" )
193+ const contentIdx = result . output . indexOf ( "<skill_content" )
194+ expect ( followupsIdx ) . toBeLessThan ( contentIdx )
195+
196+ // Skill content still present
197+ expect ( result . output ) . toContain ( `<skill_content name="dbt-develop">` )
198+ expect ( result . output ) . toContain ( "Build models with dbt." )
199+ expect ( result . output ) . toContain ( "</skill_content>" )
200+ } ,
201+ } )
202+ } finally {
203+ process . env . OPENCODE_TEST_HOME = home
204+ }
205+ } )
206+
207+ test ( "execute omits follow-up section for skills without followup mappings" , async ( ) => {
208+ await using tmp = await tmpdir ( {
209+ git : true ,
210+ init : async ( dir ) => {
211+ const skillDir = path . join ( dir , ".opencode" , "skill" , "custom-skill" )
212+ await Bun . write (
213+ path . join ( skillDir , "SKILL.md" ) ,
214+ `---
215+ name: custom-skill
216+ description: A custom skill with no followups.
217+ ---
218+
219+ # Custom Skill
220+
221+ Do custom things.
222+ ` ,
223+ )
224+ } ,
225+ } )
226+
227+ const home = process . env . OPENCODE_TEST_HOME
228+ process . env . OPENCODE_TEST_HOME = tmp . path
229+
230+ try {
231+ await Instance . provide ( {
232+ directory : tmp . path ,
233+ fn : async ( ) => {
234+ const tool = await SkillTool . init ( )
235+ const ctx : Tool . Context = {
236+ ...baseCtx ,
237+ ask : async ( ) => { } ,
238+ }
239+
240+ const result = await tool . execute ( { name : "custom-skill" } , ctx )
241+
242+ // No follow-up section
243+ expect ( result . output ) . not . toContain ( "## What's Next?" )
244+ expect ( result . output ) . not . toContain ( "/discover" )
245+
246+ // Output starts directly with skill_content
247+ expect ( result . output ) . toMatch ( / ^ < s k i l l _ c o n t e n t / )
248+ expect ( result . output ) . toContain ( "Do custom things." )
249+ } ,
250+ } )
251+ } finally {
252+ process . env . OPENCODE_TEST_HOME = home
253+ }
254+ } )
255+ // altimate_change end
256+
148257 // altimate_change start - env fingerprint skill selection config guard tests
149258 test ( "env_fingerprint_skill_selection absent (default) → selector bypassed, all skills shown" , async ( ) => {
150259 // Pre-populate cache — if selector were called, it would return this cached subset
0 commit comments