mirror of
https://github.com/anomalyco/opencode.git
synced 2026-02-01 22:48:16 +00:00
fix: merge instructions arrays across config files (#6663)
This commit is contained in:
@@ -22,13 +22,14 @@ import { ConfigMarkdown } from "./markdown"
|
||||
export namespace Config {
|
||||
const log = Log.create({ service: "config" })
|
||||
|
||||
// Custom merge function that concatenates plugin arrays instead of replacing them
|
||||
function mergeConfigWithPlugins(target: Info, source: Info): Info {
|
||||
// Custom merge function that concatenates array fields instead of replacing them
|
||||
function mergeConfigConcatArrays(target: Info, source: Info): Info {
|
||||
const merged = mergeDeep(target, source)
|
||||
// If both configs have plugin arrays, concatenate them instead of replacing
|
||||
if (target.plugin && source.plugin) {
|
||||
const pluginSet = new Set([...target.plugin, ...source.plugin])
|
||||
merged.plugin = Array.from(pluginSet)
|
||||
merged.plugin = Array.from(new Set([...target.plugin, ...source.plugin]))
|
||||
}
|
||||
if (target.instructions && source.instructions) {
|
||||
merged.instructions = Array.from(new Set([...target.instructions, ...source.instructions]))
|
||||
}
|
||||
return merged
|
||||
}
|
||||
@@ -39,19 +40,19 @@ export namespace Config {
|
||||
|
||||
// Override with custom config if provided
|
||||
if (Flag.OPENCODE_CONFIG) {
|
||||
result = mergeConfigWithPlugins(result, await loadFile(Flag.OPENCODE_CONFIG))
|
||||
result = mergeConfigConcatArrays(result, await loadFile(Flag.OPENCODE_CONFIG))
|
||||
log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG })
|
||||
}
|
||||
|
||||
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
||||
const found = await Filesystem.findUp(file, Instance.directory, Instance.worktree)
|
||||
for (const resolved of found.toReversed()) {
|
||||
result = mergeConfigWithPlugins(result, await loadFile(resolved))
|
||||
result = mergeConfigConcatArrays(result, await loadFile(resolved))
|
||||
}
|
||||
}
|
||||
|
||||
if (Flag.OPENCODE_CONFIG_CONTENT) {
|
||||
result = mergeConfigWithPlugins(result, JSON.parse(Flag.OPENCODE_CONFIG_CONTENT))
|
||||
result = mergeConfigConcatArrays(result, JSON.parse(Flag.OPENCODE_CONFIG_CONTENT))
|
||||
log.debug("loaded custom config from OPENCODE_CONFIG_CONTENT")
|
||||
}
|
||||
|
||||
@@ -59,7 +60,7 @@ export namespace Config {
|
||||
if (value.type === "wellknown") {
|
||||
process.env[value.key] = value.token
|
||||
const wellknown = (await fetch(`${key}/.well-known/opencode`).then((x) => x.json())) as any
|
||||
result = mergeConfigWithPlugins(result, await load(JSON.stringify(wellknown.config ?? {}), process.cwd()))
|
||||
result = mergeConfigConcatArrays(result, await load(JSON.stringify(wellknown.config ?? {}), process.cwd()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +95,7 @@ export namespace Config {
|
||||
if (dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) {
|
||||
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
||||
log.debug(`loading config from ${path.join(dir, file)}`)
|
||||
result = mergeConfigWithPlugins(result, await loadFile(path.join(dir, file)))
|
||||
result = mergeConfigConcatArrays(result, await loadFile(path.join(dir, file)))
|
||||
// to satisfy the type checker
|
||||
result.agent ??= {}
|
||||
result.mode ??= {}
|
||||
|
||||
@@ -488,6 +488,87 @@ Helper subagent prompt`,
|
||||
})
|
||||
})
|
||||
|
||||
test("merges instructions arrays from global and local configs", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
const projectDir = path.join(dir, "project")
|
||||
const opencodeDir = path.join(projectDir, ".opencode")
|
||||
await fs.mkdir(opencodeDir, { recursive: true })
|
||||
|
||||
await Bun.write(
|
||||
path.join(dir, "opencode.json"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
instructions: ["global-instructions.md", "shared-rules.md"],
|
||||
}),
|
||||
)
|
||||
|
||||
await Bun.write(
|
||||
path.join(opencodeDir, "opencode.json"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
instructions: ["local-instructions.md"],
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await Instance.provide({
|
||||
directory: path.join(tmp.path, "project"),
|
||||
fn: async () => {
|
||||
const config = await Config.get()
|
||||
const instructions = config.instructions ?? []
|
||||
|
||||
expect(instructions).toContain("global-instructions.md")
|
||||
expect(instructions).toContain("shared-rules.md")
|
||||
expect(instructions).toContain("local-instructions.md")
|
||||
expect(instructions.length).toBe(3)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("deduplicates duplicate instructions from global and local configs", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
const projectDir = path.join(dir, "project")
|
||||
const opencodeDir = path.join(projectDir, ".opencode")
|
||||
await fs.mkdir(opencodeDir, { recursive: true })
|
||||
|
||||
await Bun.write(
|
||||
path.join(dir, "opencode.json"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
instructions: ["duplicate.md", "global-only.md"],
|
||||
}),
|
||||
)
|
||||
|
||||
await Bun.write(
|
||||
path.join(opencodeDir, "opencode.json"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
instructions: ["duplicate.md", "local-only.md"],
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await Instance.provide({
|
||||
directory: path.join(tmp.path, "project"),
|
||||
fn: async () => {
|
||||
const config = await Config.get()
|
||||
const instructions = config.instructions ?? []
|
||||
|
||||
expect(instructions).toContain("global-only.md")
|
||||
expect(instructions).toContain("local-only.md")
|
||||
expect(instructions).toContain("duplicate.md")
|
||||
|
||||
const duplicates = instructions.filter((i) => i === "duplicate.md")
|
||||
expect(duplicates.length).toBe(1)
|
||||
expect(instructions.length).toBe(3)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("deduplicates duplicate plugins from global and local configs", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
|
||||
Reference in New Issue
Block a user