mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-16 10:30:01 +08:00
refactor(core): isolate compatibility skills
This commit is contained in:
@@ -97,7 +97,7 @@ Review endpoints in document order. For each endpoint, select one disposition an
|
||||
| [x] 018 | `GET` | `/api/command` | `command.list` | Keep | Request and response accepted as-is. |
|
||||
| [x] 019 | `GET` | `/api/skill` | `skill.list` | Keep | Renamed `location` to `path`; removed the skill-specific `slash` flag and slash-command behavior. |
|
||||
| [x] 020 | `GET` | `/api/reference` | `reference.list` | Keep | Removed duplicate `description` and `hidden` fields from nested `source`. |
|
||||
| [ ] 021 | `GET` | `/api/config` | `config.get` | | |
|
||||
| [ ] 021 | `GET` | `/api/config` | `config.get` | Change | Removed `claude` and `agents` entry variants; one compatibility plugin now normalizes their skills. |
|
||||
| [ ] 022 | `GET` | `/api/config/preferences` | `config.preferences` | | |
|
||||
| [ ] 023 | `PATCH` | `/api/config/preferences` | `config.updatePreferences` | | |
|
||||
| [ ] 024 | `GET` | `/api/config/shell` | `config.shells` | | |
|
||||
|
||||
@@ -2092,8 +2092,6 @@ export type ConfigEntry =
|
||||
}
|
||||
}
|
||||
| { type: "directory"; path: string }
|
||||
| { type: "agents"; path: string }
|
||||
| { type: "claude"; path: string }
|
||||
|
||||
export type SessionInboxUser = {
|
||||
id: string
|
||||
|
||||
+28
-20
@@ -5,17 +5,7 @@ import path from "path"
|
||||
import { isDeepStrictEqual } from "node:util"
|
||||
import { applyEdits, modify, type ParseError, parse } from "jsonc-parser"
|
||||
import { Context, Effect, FiberMap, Layer, Option, PubSub, Ref, Schema, Semaphore, Stream } from "effect"
|
||||
import {
|
||||
AgentsDirectory,
|
||||
ClaudeDirectory,
|
||||
Directory,
|
||||
Document,
|
||||
Info,
|
||||
type Preferences,
|
||||
type PreferencesPatch,
|
||||
type Entry,
|
||||
Event,
|
||||
} from "@opencode/schema/config"
|
||||
import { Directory, Document, Info, type Preferences, type PreferencesPatch, type Entry, Event } from "@opencode/schema/config"
|
||||
import { Credential } from "./credential.js"
|
||||
import { Bus } from "./bus.js"
|
||||
import { Watcher } from "./filesystem/watcher.js"
|
||||
@@ -37,6 +27,11 @@ export function latest<K extends keyof Info>(entries: readonly Entry[], key: K):
|
||||
export interface Interface {
|
||||
/** Returns location config documents and discovery sources from lowest to highest priority. */
|
||||
readonly entries: () => Effect.Effect<Entry[]>
|
||||
/** Compatibility roots consumed by internal compatibility plugins. */
|
||||
readonly compatibility?: () => Effect.Effect<{
|
||||
readonly claude: readonly AbsolutePath[]
|
||||
readonly agents: readonly AbsolutePath[]
|
||||
}>
|
||||
/**
|
||||
* Streams raw filesystem updates under config roots. Config owns root
|
||||
* topology and watch reconciliation; domain owners filter this feed for the
|
||||
@@ -71,13 +66,20 @@ export interface TestInterface extends Interface {
|
||||
export class Test extends Context.Service<Test, TestInterface>()("@opencode/Config/Test") {}
|
||||
|
||||
/** In-memory config for tests: static entries with replaceable state and a test-driven change feed. */
|
||||
export const testLayer = (initial: Entry[] = []) =>
|
||||
export const testLayer = (
|
||||
initial: Entry[] = [],
|
||||
compatibility: { readonly claude: readonly AbsolutePath[]; readonly agents: readonly AbsolutePath[] } = {
|
||||
claude: [],
|
||||
agents: [],
|
||||
},
|
||||
) =>
|
||||
Layer.effectContext(
|
||||
Effect.gen(function* () {
|
||||
const entries = yield* Ref.make(initial)
|
||||
const updates = yield* PubSub.unbounded<Watcher.Update>()
|
||||
const service = Test.of({
|
||||
entries: () => Ref.get(entries),
|
||||
compatibility: () => Effect.succeed(compatibility),
|
||||
changes: () => Stream.fromPubSub(updates),
|
||||
setEntries: (next) => Ref.set(entries, next),
|
||||
emitChange: (update) => PubSub.publish(updates, update).pipe(Effect.asVoid),
|
||||
@@ -205,8 +207,6 @@ export const layer = (options?: Options) =>
|
||||
})
|
||||
|
||||
const load = Effect.fn("Config.load")(function* (sources: ConfigDiscovery.Sources) {
|
||||
const claude = yield* Effect.filter(sources.claude, (path) => fs.isDir(path))
|
||||
const agents = yield* Effect.filter(sources.agents, (path) => fs.isDir(path))
|
||||
const direct = yield* Effect.forEach(sources.direct, (filepath) => loadFile(filepath)).pipe(
|
||||
Effect.orDie,
|
||||
Effect.map((entries) => entries.filter((entry): entry is Document => entry !== undefined)),
|
||||
@@ -244,8 +244,6 @@ export const layer = (options?: Options) =>
|
||||
)
|
||||
return [
|
||||
...(yield* loadWellknown().pipe(Effect.orDie)),
|
||||
...claude.map((path) => new ClaudeDirectory({ type: "claude", path })),
|
||||
...agents.map((path) => new AgentsDirectory({ type: "agents", path })),
|
||||
...globalSupplementary,
|
||||
...explicit,
|
||||
...direct,
|
||||
@@ -255,6 +253,7 @@ export const layer = (options?: Options) =>
|
||||
})
|
||||
|
||||
const initial = yield* ConfigDiscovery.discover(options)
|
||||
let sources = initial
|
||||
let configs = yield* load(initial)
|
||||
const updates = yield* PubSub.unbounded<Watcher.Update>()
|
||||
const reloads = yield* PubSub.sliding<void>(1)
|
||||
@@ -280,10 +279,14 @@ export const layer = (options?: Options) =>
|
||||
|
||||
const reload = Effect.fn("Config.reload")(
|
||||
function* () {
|
||||
const sources = yield* ConfigDiscovery.discover(options)
|
||||
const next = yield* load(sources)
|
||||
yield* reconcile(sources)
|
||||
if (isDeepStrictEqual(configs, next)) return
|
||||
const discovered = yield* ConfigDiscovery.discover(options)
|
||||
const next = yield* load(discovered)
|
||||
yield* reconcile(discovered)
|
||||
const compatibilityChanged =
|
||||
!isDeepStrictEqual(sources.claude, discovered.claude) ||
|
||||
!isDeepStrictEqual(sources.agents, discovered.agents)
|
||||
if (isDeepStrictEqual(configs, next) && !compatibilityChanged) return
|
||||
sources = discovered
|
||||
configs = next
|
||||
yield* bus.publish(Event.Updated, {})
|
||||
},
|
||||
@@ -379,6 +382,11 @@ export const layer = (options?: Options) =>
|
||||
entries: Effect.fnUntraced(function* () {
|
||||
return configs
|
||||
}),
|
||||
compatibility: () =>
|
||||
Effect.all({
|
||||
claude: Effect.filter(sources.claude, fs.isDir),
|
||||
agents: Effect.filter(sources.agents, fs.isDir),
|
||||
}),
|
||||
changes: () => Stream.fromPubSub(updates),
|
||||
preferences,
|
||||
updatePreferences,
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
export * as ConfigCompatibilityPlugin from "./compatibility.js"
|
||||
|
||||
import { define } from "@opencode/plugin/effect/plugin"
|
||||
import { FSUtil } from "@opencode/util/fs-util"
|
||||
import path from "path"
|
||||
import { Effect, FiberMap, PubSub, Semaphore, Stream } from "effect"
|
||||
import { Config } from "../../config.js"
|
||||
import { Watcher } from "../../filesystem/watcher.js"
|
||||
import { AbsolutePath } from "../../schema.js"
|
||||
import { Skill } from "../../skill.js"
|
||||
import { SkillFile } from "./skill-file.js"
|
||||
|
||||
export const Plugin = define({
|
||||
id: "opencode.config.compatibility",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
const config = yield* Config.Service
|
||||
const fs = yield* FSUtil.Service
|
||||
const watcher = yield* Watcher.Service
|
||||
const watches = yield* FiberMap.make<string>()
|
||||
const changes = yield* PubSub.sliding<string>(1)
|
||||
const lock = Semaphore.makeUnsafe(1)
|
||||
const skills: Skill.Info[] = []
|
||||
|
||||
const watch = Effect.fn("ConfigCompatibilityPlugin.watch")(function* (
|
||||
target: string,
|
||||
type: "file" | "directory",
|
||||
) {
|
||||
const updates = yield* watcher.subscribe({ path: target, type })
|
||||
yield* FiberMap.run(
|
||||
watches,
|
||||
`${type}:${target}`,
|
||||
updates.pipe(Stream.runForEach((update) => PubSub.publish(changes, update.path).pipe(Effect.asVoid))),
|
||||
{ onlyIfMissing: true, startImmediately: true },
|
||||
)
|
||||
})
|
||||
|
||||
const refresh = Effect.fn("ConfigCompatibilityPlugin.refresh")(
|
||||
function* () {
|
||||
yield* FiberMap.clear(watches)
|
||||
const roots = config.compatibility ? yield* config.compatibility() : { claude: [], agents: [] }
|
||||
const directories = [...roots.claude, ...roots.agents].map((root) => path.join(root, "skills"))
|
||||
const loaded = new Map<Skill.ID, Skill.Info>()
|
||||
for (const directory of directories) {
|
||||
const resolved = yield* fs.realPath(directory).pipe(Effect.orElseSucceed(() => undefined))
|
||||
if (!resolved) continue
|
||||
yield* watch(resolved, "directory")
|
||||
const files = yield* fs
|
||||
.scan("{*.md,**/SKILL.md}", { cwd: resolved, absolute: true, include: "file", symlink: true, dot: true })
|
||||
.pipe(Effect.orElseSucceed(() => [] as string[]))
|
||||
for (const filepath of files.toSorted()) {
|
||||
const content = yield* fs.readFileStringSafe(filepath).pipe(Effect.orElseSucceed(() => undefined))
|
||||
if (!content) continue
|
||||
const parsed = SkillFile.parse(resolved, filepath, content)
|
||||
if (parsed._tag === "Parsed") loaded.set(parsed.skill.id, parsed.skill)
|
||||
}
|
||||
}
|
||||
skills.splice(0, skills.length, ...loaded.values())
|
||||
},
|
||||
(effect) => lock.withPermit(effect),
|
||||
)
|
||||
|
||||
const reload = refresh().pipe(Effect.andThen(ctx.skill.reload()))
|
||||
const updates = yield* PubSub.subscribe(changes)
|
||||
yield* Stream.fromSubscription(updates).pipe(
|
||||
Stream.debounce("100 millis"),
|
||||
Stream.runForEach(() => reload),
|
||||
Effect.forkScoped({ startImmediately: true }),
|
||||
)
|
||||
yield* config.changes().pipe(
|
||||
Stream.runForEach(() => reload),
|
||||
Effect.forkScoped({ startImmediately: true }),
|
||||
)
|
||||
yield* refresh()
|
||||
yield* ctx.skill.transform((editor) => {
|
||||
for (const skill of skills) editor.add(skill)
|
||||
})
|
||||
}),
|
||||
})
|
||||
@@ -80,13 +80,8 @@ export const Plugin = define({
|
||||
if (result.some((item) => Skill.Source.equals(item, source))) return
|
||||
result.push(source)
|
||||
}
|
||||
const claude = loaded.entries.flatMap((entry) => (entry.type === "claude" ? [entry.path] : []))
|
||||
const agents = loaded.entries.flatMap((entry) => (entry.type === "agents" ? [entry.path] : []))
|
||||
const directories = loaded.entries.flatMap((entry) => (entry.type === "directory" ? [entry.path] : []))
|
||||
const items = loaded.entries.flatMap((entry) => (entry.type === "document" ? (entry.info.skills ?? []) : []))
|
||||
for (const directory of [...claude, ...agents]) {
|
||||
add(Skill.DirectorySource.make({ type: "directory", path: AbsolutePath.make(path.join(directory, "skills")) }))
|
||||
}
|
||||
for (const directory of directories) {
|
||||
add(Skill.DirectorySource.make({ type: "directory", path: AbsolutePath.make(path.join(directory, "skill")) }))
|
||||
add(Skill.DirectorySource.make({ type: "directory", path: AbsolutePath.make(path.join(directory, "skills")) }))
|
||||
|
||||
@@ -25,6 +25,7 @@ import { ConfigReferencePlugin } from "../config/plugin/reference.js"
|
||||
import { ConfigShellPlugin } from "../config/plugin/shell.js"
|
||||
import { ConfigSnapshotPlugin } from "../config/plugin/snapshot.js"
|
||||
import { ConfigSkillPlugin } from "../config/plugin/skill.js"
|
||||
import { ConfigCompatibilityPlugin } from "../config/plugin/compatibility.js"
|
||||
import { ConfigToolOutputPlugin } from "../config/plugin/tool-output.js"
|
||||
import { ConfigWebSearchPlugin } from "../config/plugin/websearch.js"
|
||||
import { ConfigWorktreePlugin } from "../config/plugin/worktree.js"
|
||||
@@ -236,6 +237,7 @@ const post = [
|
||||
ConfigShellPlugin.Plugin,
|
||||
ConfigSnapshotPlugin.Plugin,
|
||||
ConfigToolOutputPlugin.Plugin,
|
||||
ConfigCompatibilityPlugin.Plugin,
|
||||
ConfigSkillPlugin.Plugin,
|
||||
ConfigProviderPlugin.Plugin,
|
||||
ConfigWebSearchPlugin.Plugin,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { describe, expect, test } from "bun:test"
|
||||
import { Effect, Fiber, Layer, Logger, Schema, Stream } from "effect"
|
||||
import { FastCheck } from "effect/testing"
|
||||
import { Config } from "@opencode/core/config"
|
||||
import { AgentsDirectory, Directory, Document, Event, Info } from "@opencode/schema/config"
|
||||
import { Directory, Document, Event, Info } from "@opencode/schema/config"
|
||||
import { ConfigModel } from "@opencode/schema/config/model"
|
||||
import { ConfigProvider } from "@opencode/schema/config/provider"
|
||||
import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder"
|
||||
@@ -83,8 +83,6 @@ describe("Config", () => {
|
||||
const global = path.join(tmp.path, "global")
|
||||
const home = path.join(global, "home")
|
||||
const project = path.join(home, "project")
|
||||
const ambient = (entries: readonly { type: string }[]) =>
|
||||
entries.filter((entry) => entry.type === "claude" || entry.type === "agents")
|
||||
return Effect.promise(() =>
|
||||
Promise.all([
|
||||
fs.mkdir(project, { recursive: true }),
|
||||
@@ -96,7 +94,8 @@ describe("Config", () => {
|
||||
Effect.gen(function* () {
|
||||
// The fixture is real: with global enabled the walk finds both.
|
||||
const config = yield* Config.Service
|
||||
expect(ambient(yield* config.entries()).length).toBe(2)
|
||||
const compatibility = yield* config.compatibility!()
|
||||
expect([...compatibility.claude, ...compatibility.agents]).toHaveLength(2)
|
||||
}).pipe(Effect.provide(testLayer(project, global))),
|
||||
),
|
||||
Effect.andThen(
|
||||
@@ -105,7 +104,7 @@ describe("Config", () => {
|
||||
// project walk enabled.
|
||||
Effect.gen(function* () {
|
||||
const config = yield* Config.Service
|
||||
expect(ambient(yield* config.entries())).toEqual([])
|
||||
expect(yield* config.compatibility!()).toEqual({ claude: [], agents: [] })
|
||||
const watcher = yield* Watcher.Test
|
||||
expect(
|
||||
(yield* watcher.subscriptions()).filter((watch) => watch.type === "entries" && watch.path === home),
|
||||
@@ -431,7 +430,6 @@ describe("Config", () => {
|
||||
info: new Info({ model: selection("openrouter/openai/gpt-5") }),
|
||||
}),
|
||||
new Directory({ type: "directory", path: AbsolutePath.make("/skills") }),
|
||||
new AgentsDirectory({ type: "agents", path: AbsolutePath.make("/agents") }),
|
||||
new Document({ type: "document", info: new Info({}) }),
|
||||
new Document({
|
||||
type: "document",
|
||||
@@ -912,16 +910,12 @@ describe("Config", () => {
|
||||
fs.mkdir(project, { recursive: true }),
|
||||
]),
|
||||
)
|
||||
const entries = yield* Config.Service.use((config) => config.entries()).pipe(
|
||||
const compatibility = yield* Config.Service.use((config) => config.compatibility!()).pipe(
|
||||
Effect.provide(testLayer(project, global)),
|
||||
)
|
||||
|
||||
expect(entries.filter((entry) => entry.type === "claude").map((entry) => entry.path)).toEqual([
|
||||
AbsolutePath.make(path.join(home, ".claude")),
|
||||
])
|
||||
expect(entries.filter((entry) => entry.type === "agents").map((entry) => entry.path)).toEqual([
|
||||
AbsolutePath.make(path.join(home, ".agents")),
|
||||
])
|
||||
expect(compatibility.claude).toEqual([AbsolutePath.make(path.join(home, ".claude"))])
|
||||
expect(compatibility.agents).toEqual([AbsolutePath.make(path.join(home, ".agents"))])
|
||||
}),
|
||||
),
|
||||
),
|
||||
@@ -1560,18 +1554,19 @@ describe("Config", () => {
|
||||
const config = yield* Config.Service
|
||||
const entries = (yield* config.entries()).filter((entry) => !entry.path || inFixture(tmp.path, entry.path))
|
||||
const documents = entries.filter((entry) => entry.type === "document")
|
||||
const compatibility = yield* config.compatibility!()
|
||||
|
||||
expect(entries.filter((entry) => entry.type === "directory").map((entry) => entry.path)).toEqual([
|
||||
AbsolutePath.make(global),
|
||||
AbsolutePath.make(path.join(root, ".opencode")),
|
||||
AbsolutePath.make(path.join(directory, ".opencode")),
|
||||
])
|
||||
expect(entries.filter((entry) => entry.type === "agents").map((entry) => entry.path)).toEqual([
|
||||
expect(compatibility.agents.filter((path) => inFixture(tmp.path, path))).toEqual([
|
||||
AbsolutePath.make(globalAgents),
|
||||
AbsolutePath.make(path.join(root, ".agents")),
|
||||
AbsolutePath.make(path.join(directory, ".agents")),
|
||||
])
|
||||
expect(entries.filter((entry) => entry.type === "claude").map((entry) => entry.path)).toEqual([
|
||||
expect(compatibility.claude.filter((path) => inFixture(tmp.path, path))).toEqual([
|
||||
AbsolutePath.make(globalClaude),
|
||||
AbsolutePath.make(path.join(root, ".claude")),
|
||||
AbsolutePath.make(path.join(directory, ".claude")),
|
||||
@@ -1586,12 +1581,6 @@ describe("Config", () => {
|
||||
"directory-dot",
|
||||
])
|
||||
expect(entries.map((entry) => (entry.type === "document" ? entry.info.$schema : entry.path))).toEqual([
|
||||
AbsolutePath.make(globalClaude),
|
||||
AbsolutePath.make(path.join(root, ".claude")),
|
||||
AbsolutePath.make(path.join(directory, ".claude")),
|
||||
AbsolutePath.make(globalAgents),
|
||||
AbsolutePath.make(path.join(root, ".agents")),
|
||||
AbsolutePath.make(path.join(directory, ".agents")),
|
||||
"global",
|
||||
AbsolutePath.make(global),
|
||||
"outside",
|
||||
|
||||
@@ -3,8 +3,9 @@ import path from "path"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Deferred, Effect, Fiber, Layer, Schema, Stream } from "effect"
|
||||
import { Config } from "@opencode/core/config"
|
||||
import { AgentsDirectory, ClaudeDirectory, Directory, Document, type Entry, Info } from "@opencode/schema/config"
|
||||
import { Directory, Document, type Entry, Info } from "@opencode/schema/config"
|
||||
import { ConfigSkillPlugin } from "@opencode/core/config/plugin/skill"
|
||||
import { ConfigCompatibilityPlugin } from "@opencode/core/config/plugin/compatibility"
|
||||
import { SkillFile } from "@opencode/core/config/plugin/skill-file"
|
||||
import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder"
|
||||
import { Watcher } from "@opencode/core/filesystem/watcher"
|
||||
@@ -47,18 +48,26 @@ const startEntries = Effect.fnUntraced(function* (
|
||||
directory: string,
|
||||
home = directory,
|
||||
discovery = emptyDiscovery,
|
||||
compatibility: { readonly claude: readonly AbsolutePath[]; readonly agents: readonly AbsolutePath[] } = {
|
||||
claude: [],
|
||||
agents: [],
|
||||
},
|
||||
) {
|
||||
const service = yield* Skill.Service
|
||||
const pluginHost = host({
|
||||
skill: {
|
||||
list: () => Effect.die("unused skill.list"),
|
||||
transform: service.transform,
|
||||
reload: service.reload,
|
||||
},
|
||||
})
|
||||
yield* ConfigCompatibilityPlugin.Plugin.effect(pluginHost).pipe(
|
||||
Effect.provide(Config.testLayer(entries, compatibility)),
|
||||
)
|
||||
yield* ConfigSkillPlugin.Plugin.effect(
|
||||
host({
|
||||
skill: {
|
||||
list: () => Effect.die("unused skill.list"),
|
||||
transform: service.transform,
|
||||
reload: service.reload,
|
||||
},
|
||||
}),
|
||||
pluginHost,
|
||||
).pipe(
|
||||
Effect.provide(Config.testLayer(entries)),
|
||||
Effect.provide(Config.testLayer(entries, compatibility)),
|
||||
Effect.provideService(SkillDiscovery.Service, discovery),
|
||||
Effect.provideService(Global.Service, Global.Service.of({ ...Global.make(), home })),
|
||||
Effect.provideService(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make(directory) }))),
|
||||
@@ -82,7 +91,7 @@ const start = (skills: string[], directory: string, discovery = emptyDiscovery)
|
||||
const discover = (directory: string, global: string) =>
|
||||
Effect.gen(function* () {
|
||||
const config = yield* Config.Service
|
||||
return yield* config.entries()
|
||||
return { entries: yield* config.entries(), compatibility: yield* config.compatibility!() }
|
||||
}).pipe(
|
||||
Effect.provide(
|
||||
AppNodeBuilder.build(LayerNode.group([Config.node, Bus.node]), [
|
||||
@@ -175,13 +184,13 @@ describe("ConfigSkillPlugin.Plugin", () => {
|
||||
|
||||
yield* startEntries(
|
||||
[
|
||||
new ClaudeDirectory({ type: "claude", path: AbsolutePath.make(claude) }),
|
||||
new AgentsDirectory({ type: "agents", path: AbsolutePath.make(agents) }),
|
||||
new Directory({ type: "directory", path: AbsolutePath.make(opencode) }),
|
||||
new Document({ type: "document", info: decode({ skills: ["~/shared", "./relative"] }) }),
|
||||
],
|
||||
directory,
|
||||
home,
|
||||
emptyDiscovery,
|
||||
{ claude: [AbsolutePath.make(claude)], agents: [AbsolutePath.make(agents)] },
|
||||
)
|
||||
const watcher = yield* Watcher.Test
|
||||
expect(yield* watcher.subscriptions()).toEqual(expected.map((item) => ({ path: item, type: "directory" })))
|
||||
@@ -245,8 +254,14 @@ describe("ConfigSkillPlugin.Plugin", () => {
|
||||
await write(worktreeSkills, "review", "Worktree")
|
||||
})
|
||||
|
||||
const entries = yield* discover(worktree, path.join(tmp.path, "global"))
|
||||
const skill = yield* startEntries(entries, worktree)
|
||||
const discovered = yield* discover(worktree, path.join(tmp.path, "global"))
|
||||
const skill = yield* startEntries(
|
||||
discovered.entries,
|
||||
worktree,
|
||||
worktree,
|
||||
emptyDiscovery,
|
||||
discovered.compatibility,
|
||||
)
|
||||
const review = (yield* skill.list()).find((item) => item.id === "review")
|
||||
|
||||
expect(review?.description).toBe("Worktree")
|
||||
|
||||
@@ -13048,34 +13048,6 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.AgentsDirectoryEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["agents"]
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["type", "path"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.ClaudeDirectoryEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["claude"]
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["type", "path"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.CommandEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -13164,12 +13136,6 @@
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Config.DirectoryEncoded"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Config.AgentsDirectoryEncoded"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Config.ClaudeDirectoryEncoded"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -132,17 +132,7 @@ export class Directory extends Schema.Class<Directory>("Config.Directory")({
|
||||
path: AbsolutePath,
|
||||
}) {}
|
||||
|
||||
export class AgentsDirectory extends Schema.Class<AgentsDirectory>("Config.AgentsDirectory")({
|
||||
type: Schema.Literal("agents"),
|
||||
path: AbsolutePath,
|
||||
}) {}
|
||||
|
||||
export class ClaudeDirectory extends Schema.Class<ClaudeDirectory>("Config.ClaudeDirectory")({
|
||||
type: Schema.Literal("claude"),
|
||||
path: AbsolutePath,
|
||||
}) {}
|
||||
|
||||
export const Entry = Schema.Union([Document, Directory, AgentsDirectory, ClaudeDirectory]).annotate({
|
||||
export const Entry = Schema.Union([Document, Directory]).annotate({
|
||||
identifier: "Config.Entry",
|
||||
})
|
||||
export type Entry = typeof Entry.Type
|
||||
|
||||
@@ -49,8 +49,6 @@ describe("Config.Entry", () => {
|
||||
}),
|
||||
new Config.Document({ type: "document", info: new Config.Info({ shell: "/bin/zsh" }) }),
|
||||
new Config.Directory({ type: "directory", path: AbsolutePath.make("/project/.opencode") }),
|
||||
new Config.AgentsDirectory({ type: "agents", path: AbsolutePath.make("/project/.agents") }),
|
||||
new Config.ClaudeDirectory({ type: "claude", path: AbsolutePath.make("/project/.claude") }),
|
||||
]
|
||||
|
||||
const encoded = Schema.encodeSync(Schema.Array(Config.Entry))(entries)
|
||||
@@ -59,7 +57,7 @@ describe("Config.Entry", () => {
|
||||
expect(decoded).toEqual(entries)
|
||||
expect(decoded[0]).toBeInstanceOf(Config.Document)
|
||||
expect(decoded[1]).not.toHaveProperty("path")
|
||||
expect(decoded.map((entry) => entry.type)).toEqual(["document", "document", "directory", "agents", "claude"])
|
||||
expect(decoded.map((entry) => entry.type)).toEqual(["document", "document", "directory"])
|
||||
expect(decoded[0]?.type === "document" ? decoded[0].info.permissions : undefined).toEqual([
|
||||
{ action: "shell", resource: "*", effect: "ask" },
|
||||
{ action: "shell", resource: "git status", effect: "allow" },
|
||||
|
||||
@@ -13048,34 +13048,6 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.AgentsDirectoryEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["agents"]
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["type", "path"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.ClaudeDirectoryEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["claude"]
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["type", "path"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.CommandEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -13164,12 +13136,6 @@
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Config.DirectoryEncoded"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Config.AgentsDirectoryEncoded"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Config.ClaudeDirectoryEncoded"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -13048,34 +13048,6 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.AgentsDirectoryEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["agents"]
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["type", "path"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.ClaudeDirectoryEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["claude"]
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["type", "path"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.CommandEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -13164,12 +13136,6 @@
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Config.DirectoryEncoded"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Config.AgentsDirectoryEncoded"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Config.ClaudeDirectoryEncoded"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user