From cccdeef2945ee2c08b63156ae4552e9726551ddc Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Fri, 15 May 2026 02:47:26 +0530 Subject: [PATCH] refactor(flags): migrate claude code skills flag to RuntimeFlags (#27605) --- packages/core/src/flag/flag.ts | 3 - packages/opencode/src/effect/runtime-flags.ts | 4 ++ packages/opencode/src/skill/index.ts | 16 +++++- .../test/effect/runtime-flags.test.ts | 28 ++++++++++ packages/opencode/test/skill/skill.test.ts | 56 +++++++++++++++++++ 5 files changed, 102 insertions(+), 5 deletions(-) diff --git a/packages/core/src/flag/flag.ts b/packages/core/src/flag/flag.ts index ec417cf5bd..df276ff88a 100644 --- a/packages/core/src/flag/flag.ts +++ b/packages/core/src/flag/flag.ts @@ -14,8 +14,6 @@ function number(key: string) { const OPENCODE_EXPERIMENTAL = truthy("OPENCODE_EXPERIMENTAL") const OPENCODE_DISABLE_CLAUDE_CODE = truthy("OPENCODE_DISABLE_CLAUDE_CODE") -const OPENCODE_DISABLE_CLAUDE_CODE_SKILLS = - OPENCODE_DISABLE_CLAUDE_CODE || truthy("OPENCODE_DISABLE_CLAUDE_CODE_SKILLS") const copy = process.env["OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT"] export const Flag = { @@ -41,7 +39,6 @@ export const Flag = { OPENCODE_DISABLE_MOUSE: truthy("OPENCODE_DISABLE_MOUSE"), OPENCODE_DISABLE_CLAUDE_CODE, OPENCODE_DISABLE_CLAUDE_CODE_PROMPT: OPENCODE_DISABLE_CLAUDE_CODE || truthy("OPENCODE_DISABLE_CLAUDE_CODE_PROMPT"), - OPENCODE_DISABLE_CLAUDE_CODE_SKILLS, OPENCODE_DISABLE_EXTERNAL_SKILLS: truthy("OPENCODE_DISABLE_EXTERNAL_SKILLS"), OPENCODE_FAKE_VCS: process.env["OPENCODE_FAKE_VCS"], OPENCODE_SERVER_PASSWORD: process.env["OPENCODE_SERVER_PASSWORD"], diff --git a/packages/opencode/src/effect/runtime-flags.ts b/packages/opencode/src/effect/runtime-flags.ts index 703de0de22..4228a32f83 100644 --- a/packages/opencode/src/effect/runtime-flags.ts +++ b/packages/opencode/src/effect/runtime-flags.ts @@ -9,6 +9,10 @@ const enabledByExperimental = (name: string) => export class Service extends ConfigService.Service()("@opencode/RuntimeFlags", { pure: bool("OPENCODE_PURE"), disableDefaultPlugins: bool("OPENCODE_DISABLE_DEFAULT_PLUGINS"), + disableClaudeCodeSkills: Config.all({ + broad: bool("OPENCODE_DISABLE_CLAUDE_CODE"), + direct: bool("OPENCODE_DISABLE_CLAUDE_CODE_SKILLS"), + }).pipe(Config.map((flags) => flags.broad || flags.direct)), enableExa: Config.all({ experimental, enabled: bool("OPENCODE_ENABLE_EXA"), diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index 59dfeb0804..7347707949 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -11,6 +11,7 @@ import { Permission } from "@/permission" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Config } from "@/config/config" import { ConfigMarkdown } from "@/config/markdown" +import { RuntimeFlags } from "@/effect/runtime-flags" import { Glob } from "@opencode-ai/core/util/glob" import * as Log from "@opencode-ai/core/util/log" import { Discovery } from "./discovery" @@ -165,6 +166,7 @@ const discoverSkills = Effect.fnUntraced(function* ( discovery: Discovery.Interface, fsys: AppFileSystem.Interface, global: Global.Interface, + disableClaudeCodeSkills: boolean, directory: string, worktree: string, ) { @@ -172,7 +174,7 @@ const discoverSkills = Effect.fnUntraced(function* ( const externalDirs: string[] = [] if (!Flag.OPENCODE_DISABLE_EXTERNAL_SKILLS) { - if (!Flag.OPENCODE_DISABLE_CLAUDE_CODE_SKILLS) externalDirs.push(CLAUDE_EXTERNAL_DIR) + if (!disableClaudeCodeSkills) externalDirs.push(CLAUDE_EXTERNAL_DIR) externalDirs.push(AGENTS_EXTERNAL_DIR) for (const dir of externalDirs) { @@ -239,9 +241,18 @@ export const layer = Layer.effect( const bus = yield* Bus.Service const fsys = yield* AppFileSystem.Service const global = yield* Global.Service + const flags = yield* RuntimeFlags.Service const discovered = yield* InstanceState.make( Effect.fn("Skill.discovery")(function* (ctx) { - return yield* discoverSkills(config, discovery, fsys, global, ctx.directory, ctx.worktree) + return yield* discoverSkills( + config, + discovery, + fsys, + global, + flags.disableClaudeCodeSkills, + ctx.directory, + ctx.worktree, + ) }), ) const state = yield* InstanceState.make( @@ -291,6 +302,7 @@ export const defaultLayer = layer.pipe( Layer.provide(Bus.layer), Layer.provide(AppFileSystem.defaultLayer), Layer.provide(Global.layer), + Layer.provide(RuntimeFlags.defaultLayer), ) export function fmt(list: Info[], opts: { verbose: boolean }) { diff --git a/packages/opencode/test/effect/runtime-flags.test.ts b/packages/opencode/test/effect/runtime-flags.test.ts index 817ad2b1af..aa611ee81d 100644 --- a/packages/opencode/test/effect/runtime-flags.test.ts +++ b/packages/opencode/test/effect/runtime-flags.test.ts @@ -46,6 +46,7 @@ describe("RuntimeFlags", () => { expect(flags.pure).toBe(false) expect(flags.disableDefaultPlugins).toBe(true) + expect(flags.disableClaudeCodeSkills).toBe(false) expect(flags.enableExa).toBe(false) expect(flags.client).toBe("cli") }), @@ -70,8 +71,35 @@ describe("RuntimeFlags", () => { expect(flags.pure).toBe(false) expect(flags.disableDefaultPlugins).toBe(false) + expect(flags.disableClaudeCodeSkills).toBe(false) expect(flags.enableExa).toBe(false) expect(flags.client).toBe("cli") }), ) + + it.effect("disableClaudeCodeSkills defaults to false", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({}))) + + expect(flags.disableClaudeCodeSkills).toBe(false) + }), + ) + + it.effect("disableClaudeCodeSkills reads OPENCODE_DISABLE_CLAUDE_CODE_SKILLS", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe( + Effect.provide(fromConfig({ OPENCODE_DISABLE_CLAUDE_CODE_SKILLS: "true" })), + ) + + expect(flags.disableClaudeCodeSkills).toBe(true) + }), + ) + + it.effect("disableClaudeCodeSkills inherits OPENCODE_DISABLE_CLAUDE_CODE", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_DISABLE_CLAUDE_CODE: "true" }))) + + expect(flags.disableClaudeCodeSkills).toBe(true) + }), + ) }) diff --git a/packages/opencode/test/skill/skill.test.ts b/packages/opencode/test/skill/skill.test.ts index 969014e6b3..bc955728bb 100644 --- a/packages/opencode/test/skill/skill.test.ts +++ b/packages/opencode/test/skill/skill.test.ts @@ -1,7 +1,13 @@ import { describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { Skill } from "../../src/skill" +import { Discovery } from "../../src/skill/discovery" +import { RuntimeFlags } from "../../src/effect/runtime-flags" +import { Bus } from "../../src/bus" +import { Config } from "../../src/config/config" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { AppFileSystem } from "@opencode-ai/core/filesystem" +import { Global } from "@opencode-ai/core/global" import { provideInstance, provideTmpdirInstance, tmpdir } from "../fixture/fixture" import { testEffect } from "../lib/effect" import path from "path" @@ -10,6 +16,19 @@ import fs from "fs/promises" const node = CrossSpawnSpawner.defaultLayer const it = testEffect(Layer.mergeAll(Skill.defaultLayer, node)) +const itWithoutClaudeCodeSkills = testEffect( + Layer.mergeAll( + Skill.layer.pipe( + Layer.provide(Discovery.defaultLayer), + Layer.provide(Config.defaultLayer), + Layer.provide(Bus.layer), + Layer.provide(AppFileSystem.defaultLayer), + Layer.provide(Global.layer), + Layer.provide(RuntimeFlags.layer({ disableClaudeCodeSkills: true })), + ), + node, + ), +) async function createGlobalSkill(homeDir: string) { const skillDir = path.join(homeDir, ".claude", "skills", "global-test-skill") @@ -364,6 +383,43 @@ description: A skill in the .agents/skills directory. ), ) + itWithoutClaudeCodeSkills.live("skips Claude Code skills when disabled", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + yield* Effect.promise(() => + Promise.all([ + Bun.write( + path.join(dir, ".claude", "skills", "claude-skill", "SKILL.md"), + `--- +name: claude-skill +description: A skill in the .claude/skills directory. +--- + +# Claude Skill +`, + ), + Bun.write( + path.join(dir, ".agents", "skills", "agent-skill", "SKILL.md"), + `--- +name: agent-skill +description: A skill in the .agents/skills directory. +--- + +# Agent Skill +`, + ), + ]), + ) + + const skill = yield* Skill.Service + const list = (yield* skill.all()).filter((s) => s.location !== "") + expect(list.map((s) => s.name)).toEqual(["agent-skill"]) + }), + { git: true }, + ), + ) + it.live("properly resolves directories that skills live in", () => provideTmpdirInstance( (dir) =>