diff --git a/src/hooks/auto-slash-command/init-deep-skill.test.ts b/src/hooks/auto-slash-command/init-deep-skill.test.ts new file mode 100644 index 000000000..2730255c8 --- /dev/null +++ b/src/hooks/auto-slash-command/init-deep-skill.test.ts @@ -0,0 +1,43 @@ +/// + +import { describe, expect, test } from "bun:test" +import { createBuiltinSkills } from "../../features/builtin-skills" +import type { LoadedSkill } from "../../features/opencode-skill-loader" +import { executeSlashCommand } from "./executor" + +function asLoadedSkill(name: string): LoadedSkill { + const skill = createBuiltinSkills().find((candidate) => candidate.name === name) + if (!skill) { + throw new Error(`missing builtin skill: ${name}`) + } + + return { + name: skill.name, + definition: { + name: skill.name, + description: skill.description, + template: skill.template, + argumentHint: skill.argumentHint, + }, + scope: "builtin", + } +} + +describe("init-deep slash surface", () => { + test("#given init-deep builtin skill #when slash command executes #then it renders skill instructions with arguments", async () => { + // given + const skills = [asLoadedSkill("init-deep")] + + // when + const result = await executeSlashCommand( + { command: "init-deep", args: "--max-depth=2", raw: "/init-deep --max-depth=2" }, + { skills, pluginsEnabled: false }, + ) + + // then + expect(result.success).toBe(true) + expect(result.replacementText).toContain("**Scope**: skill") + expect(result.replacementText).toContain("--max-depth=2") + expect(result.replacementText).toContain("Generate hierarchical AGENTS.md files") + }) +}) diff --git a/src/hooks/start-work/start-work-hook.test.ts b/src/hooks/start-work/start-work-hook.test.ts new file mode 100644 index 000000000..007823dd3 --- /dev/null +++ b/src/hooks/start-work/start-work-hook.test.ts @@ -0,0 +1,58 @@ +/// + +import { describe, expect, test, beforeEach, afterEach } from "bun:test" +import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs" +import { join } from "node:path" +import { tmpdir } from "node:os" +import { randomUUID } from "node:crypto" +import { readBoulderState, clearBoulderState } from "../../features/boulder-state" +import { unsafeTestValue } from "../../../test-support/unsafe-test-value" +import { createStartWorkHook } from "./start-work-hook" + +describe("start-work hook platform session ids", () => { + let testDir: string + + function createStartWorkPrompt(): string { + return ` +You are starting a Sisyphus work session. + + +` + } + + beforeEach(() => { + testDir = join(tmpdir(), `start-work-hook-session-prefix-${randomUUID()}`) + mkdirSync(join(testDir, ".omo", "plans"), { recursive: true }) + clearBoulderState(testDir) + }) + + afterEach(() => { + clearBoulderState(testDir) + if (existsSync(testDir)) { + rmSync(testDir, { recursive: true, force: true }) + } + }) + + test("#given raw chat session id #when processing start-work template #then boulder stores opencode-prefixed id", async () => { + // given + writeFileSync(join(testDir, ".omo", "plans", "work.md"), "# Work\n- [ ] First task\n") + const hook = createStartWorkHook(unsafeTestValue[0]>({ + directory: testDir, + client: { + session: { + messages: async () => ({ data: [] }), + }, + }, + })) + const output = { + parts: [{ type: "text", text: createStartWorkPrompt() }], + } + + // when + await hook["chat.message"]({ sessionID: "raw-sess" }, output) + const state = readBoulderState(testDir) + + // then + expect(state?.session_ids).toEqual(["opencode:raw-sess"]) + }) +})