Skip to content

Commit 9341cae

Browse files
anandgupta42claude
andcommitted
test: add e2e tests for skill follow-up suggestions via SkillTool.execute
- Test that `dbt-develop` skill output includes follow-ups before `<skill_content>` - Test that unmapped skills produce no follow-up section - Both tests invoke the real `SkillTool.execute` path end-to-end Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 930a417 commit 9341cae

1 file changed

Lines changed: 109 additions & 0 deletions

File tree

packages/opencode/test/tool/skill.test.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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(/^<skill_content/)
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

Comments
 (0)