From d0639baecadd285ca2501a5a76bcc3394dc40b42 Mon Sep 17 00:00:00 2001 From: ismeth Date: Fri, 20 Feb 2026 21:53:05 +0100 Subject: [PATCH] fix: enforce directory param in skill resolution, replace legacy k2p5 model ID - Make directory required in SkillLoadOptions, getAllSkills, and async skill template resolvers to prevent unsafe process.cwd() fallback - Remove dead skill export and process.cwd() fallback in skill tool - Replace kimi-for-coding/k2p5 with kimi-for-coding/kimi-k2.5 in council-members-generator --- src/cli/council-members-generator.ts | 2 +- src/features/opencode-skill-loader/skill-content.test.ts | 8 ++++---- src/features/opencode-skill-loader/skill-discovery.ts | 4 ++-- .../opencode-skill-loader/skill-resolution-options.ts | 2 +- .../opencode-skill-loader/skill-template-resolver.ts | 4 ++-- src/tools/delegate-task/skill-resolver.ts | 2 +- src/tools/skill/index.ts | 2 +- src/tools/skill/tools.ts | 7 +++---- src/tools/skill/types.ts | 4 ++-- 9 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/cli/council-members-generator.ts b/src/cli/council-members-generator.ts index 66ad95720..2f6c25ef1 100644 --- a/src/cli/council-members-generator.ts +++ b/src/cli/council-members-generator.ts @@ -27,7 +27,7 @@ const COUNCIL_CANDIDATES: Array<{ }, { provider: (a) => a.kimiForCoding, - model: "kimi-for-coding/k2p5", + model: "kimi-for-coding/kimi-k2.5", name: "Kimi 2.5", } ] diff --git a/src/features/opencode-skill-loader/skill-content.test.ts b/src/features/opencode-skill-loader/skill-content.test.ts index 64d6d5bf4..1fb5cc3ff 100644 --- a/src/features/opencode-skill-loader/skill-content.test.ts +++ b/src/features/opencode-skill-loader/skill-content.test.ts @@ -185,7 +185,7 @@ describe("resolveMultipleSkillsAsync", () => { const skillNames = ["playwright", "git-master"] // when: resolving multiple skills async - const result = await resolveMultipleSkillsAsync(skillNames) + const result = await resolveMultipleSkillsAsync(skillNames, { directory: process.cwd() }) // then: all builtin skills resolved expect(result.resolved.size).toBe(2) @@ -199,7 +199,7 @@ describe("resolveMultipleSkillsAsync", () => { const skillNames = ["playwright", "nonexistent-skill-12345"] // when: resolving multiple skills async - const result = await resolveMultipleSkillsAsync(skillNames) + const result = await resolveMultipleSkillsAsync(skillNames, { directory: process.cwd() }) // then: existing skills resolved, non-existing in notFound expect(result.resolved.size).toBe(1) @@ -289,7 +289,7 @@ describe("resolveMultipleSkillsAsync", () => { const skillNames = ["git-master"] // when: resolving without any gitMasterConfig - const result = await resolveMultipleSkillsAsync(skillNames) + const result = await resolveMultipleSkillsAsync(skillNames, { directory: process.cwd() }) // then: watermark is injected (default is ON) expect(result.resolved.size).toBe(1) @@ -363,7 +363,7 @@ describe("resolveMultipleSkillsAsync", () => { const skillNames: string[] = [] // when: resolving multiple skills async - const result = await resolveMultipleSkillsAsync(skillNames) + const result = await resolveMultipleSkillsAsync(skillNames, { directory: process.cwd() }) // then: empty results expect(result.resolved.size).toBe(0) diff --git a/src/features/opencode-skill-loader/skill-discovery.ts b/src/features/opencode-skill-loader/skill-discovery.ts index 2ee2cbca3..2dab0c6c9 100644 --- a/src/features/opencode-skill-loader/skill-discovery.ts +++ b/src/features/opencode-skill-loader/skill-discovery.ts @@ -9,8 +9,8 @@ export function clearSkillCache(): void { skillCache.clear() } -export async function getAllSkills(options?: SkillResolutionOptions): Promise { - const directory = options?.directory ?? process.cwd() +export async function getAllSkills(options: SkillResolutionOptions & { directory: string }): Promise { + const directory = options.directory const cacheKey = `${options?.browserProvider ?? "playwright"}:${directory}` const hasDisabledSkills = options?.disabledSkills && options.disabledSkills.size > 0 diff --git a/src/features/opencode-skill-loader/skill-resolution-options.ts b/src/features/opencode-skill-loader/skill-resolution-options.ts index e2ba58ecd..53f93b78f 100644 --- a/src/features/opencode-skill-loader/skill-resolution-options.ts +++ b/src/features/opencode-skill-loader/skill-resolution-options.ts @@ -4,6 +4,6 @@ export interface SkillResolutionOptions { gitMasterConfig?: GitMasterConfig browserProvider?: BrowserAutomationProvider disabledSkills?: Set - /** Project directory to discover project-level skills from. Falls back to process.cwd() if not provided. */ + /** Project directory to discover project-level skills from. Required for async resolution — process.cwd() is unsafe in OpenCode. */ directory?: string } diff --git a/src/features/opencode-skill-loader/skill-template-resolver.ts b/src/features/opencode-skill-loader/skill-template-resolver.ts index 046256c37..7a5132737 100644 --- a/src/features/opencode-skill-loader/skill-template-resolver.ts +++ b/src/features/opencode-skill-loader/skill-template-resolver.ts @@ -51,7 +51,7 @@ export function resolveMultipleSkills( export async function resolveSkillContentAsync( skillName: string, - options?: SkillResolutionOptions + options: SkillResolutionOptions & { directory: string } ): Promise { const allSkills = await getAllSkills(options) const skill = allSkills.find((loadedSkill) => loadedSkill.name === skillName) @@ -68,7 +68,7 @@ export async function resolveSkillContentAsync( export async function resolveMultipleSkillsAsync( skillNames: string[], - options?: SkillResolutionOptions + options: SkillResolutionOptions & { directory: string } ): Promise<{ resolved: Map; notFound: string[] }> { const allSkills = await getAllSkills(options) const skillMap = new Map() diff --git a/src/tools/delegate-task/skill-resolver.ts b/src/tools/delegate-task/skill-resolver.ts index e3bb89a50..8155d6d69 100644 --- a/src/tools/delegate-task/skill-resolver.ts +++ b/src/tools/delegate-task/skill-resolver.ts @@ -4,7 +4,7 @@ import { discoverSkills } from "../../features/opencode-skill-loader" export async function resolveSkillContent( skills: string[], - options: { gitMasterConfig?: GitMasterConfig; browserProvider?: BrowserAutomationProvider, disabledSkills?: Set, directory?: string } + options: { gitMasterConfig?: GitMasterConfig; browserProvider?: BrowserAutomationProvider; disabledSkills?: Set; directory: string } ): Promise<{ content: string | undefined; contents: string[]; error: string | null }> { if (skills.length === 0) { return { content: undefined, contents: [], error: null } diff --git a/src/tools/skill/index.ts b/src/tools/skill/index.ts index 3c32b1c2e..c5d035fcd 100644 --- a/src/tools/skill/index.ts +++ b/src/tools/skill/index.ts @@ -1,3 +1,3 @@ export * from "./constants" export * from "./types" -export { skill, createSkillTool } from "./tools" +export { createSkillTool } from "./tools" diff --git a/src/tools/skill/tools.ts b/src/tools/skill/tools.ts index ec1dfc62c..09614da9d 100644 --- a/src/tools/skill/tools.ts +++ b/src/tools/skill/tools.ts @@ -24,7 +24,7 @@ import { mergeNativeSkills, } from "./native-skills" -export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition { +export function createSkillTool(options: SkillLoadOptions): ToolDefinition { let cachedDescription: string | null = null const getSkills = async (): Promise => { @@ -32,6 +32,7 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition const discovered = await getAllSkills({ disabledSkills: options?.disabledSkills, browserProvider: options?.browserProvider, + directory: options.directory, }) const allSkills = !options.skills ? discovered @@ -138,7 +139,7 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition body = injectGitMasterConfig(body, options.gitMasterConfig) } - const dir = matchedSkill.path ? dirname(matchedSkill.path) : matchedSkill.resolvedPath || options.directory || process.cwd() + const dir = matchedSkill.path ? dirname(matchedSkill.path) : matchedSkill.resolvedPath || options.directory const output = [ `## Skill: ${matchedSkill.name}`, @@ -192,5 +193,3 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition }, }) } - -export const skill: ToolDefinition = createSkillTool() diff --git a/src/tools/skill/types.ts b/src/tools/skill/types.ts index 0ac4e5bc8..5299888e2 100644 --- a/src/tools/skill/types.ts +++ b/src/tools/skill/types.ts @@ -45,6 +45,6 @@ export interface SkillLoadOptions { get(name: string): { name: string; description: string; location: string; content: string } | undefined | Promise<{ name: string; description: string; location: string; content: string } | undefined> dirs(): string[] | Promise } - /** Project directory for skill discovery and base directory resolution. Should be ctx.directory from PluginContext. */ - directory?: string + /** Project directory for skill discovery and base directory resolution. Must be ctx.directory from PluginContext -- process.cwd() is unsafe in OpenCode. */ + directory: string }