diff --git a/packages/coding-agent/src/discovery/builtin.ts b/packages/coding-agent/src/discovery/builtin.ts index 64d2fe5b1..33ee2d261 100644 --- a/packages/coding-agent/src/discovery/builtin.ts +++ b/packages/coding-agent/src/discovery/builtin.ts @@ -5,6 +5,7 @@ */ import * as path from "node:path"; import { logger, parseFrontmatter, tryParseJson } from "@oh-my-pi/pi-utils"; +import { YAML } from "bun"; import { registerProvider } from "../capability"; import { type ContextFile, contextFileCapability } from "../capability/context-file"; import { type Extension, type ExtensionManifest, extensionCapability } from "../capability/extension"; @@ -778,22 +779,46 @@ async function loadSettings(ctx: LoadContext): Promise> { const items: Settings[] = []; const warnings: string[] = []; + const parseYamlSettings = (content: string, filePath: string): Record | null => { + try { + const data = YAML.parse(content); + if (!data || typeof data !== "object" || Array.isArray(data)) return {}; + return data as Record; + } catch { + warnings.push(`Failed to parse ${filePath}`); + return null; + } + }; + for (const { dir, level } of await getConfigDirs(ctx)) { const settingsPath = path.join(dir, "settings.json"); - const content = await readFile(settingsPath); - if (!content) continue; - - const data = tryParseJson>(content); - if (!data) { - warnings.push(`Failed to parse ${settingsPath}`); - continue; + const settingsContent = await readFile(settingsPath); + if (settingsContent) { + const data = tryParseJson>(settingsContent); + if (data) { + items.push({ + path: settingsPath, + data, + level, + _source: createSourceMeta(PROVIDER_ID, settingsPath, level), + }); + } else { + warnings.push(`Failed to parse ${settingsPath}`); + } } + const configPath = path.join(dir, "config.yml"); + const configContent = await readFile(configPath); + if (!configContent) continue; + + const data = parseYamlSettings(configContent, configPath); + if (!data) continue; + items.push({ - path: settingsPath, + path: configPath, data, level, - _source: createSourceMeta(PROVIDER_ID, settingsPath, level), + _source: createSourceMeta(PROVIDER_ID, configPath, level), }); } diff --git a/packages/coding-agent/test/autocomplete-max-visible.test.ts b/packages/coding-agent/test/autocomplete-max-visible.test.ts index 4e03f0614..25b536a06 100644 --- a/packages/coding-agent/test/autocomplete-max-visible.test.ts +++ b/packages/coding-agent/test/autocomplete-max-visible.test.ts @@ -54,6 +54,15 @@ describe("autocompleteMaxVisible setting", () => { const settings = await Settings.init({ cwd: projectDir, agentDir }); expect(settings.get("autocompleteMaxVisible")).toBe(15); }); + + it("should let project config.yml override global config.yml", async () => { + await Bun.write(path.join(agentDir, "config.yml"), YAML.stringify({ autocompleteMaxVisible: 15 }, null, 2)); + await Bun.write(path.join(getProjectAgentDir(projectDir), "config.yml"), YAML.stringify({ autocompleteMaxVisible: 20 }, null, 2)); + + const settings = await Settings.init({ cwd: projectDir, agentDir }); + + expect(settings.get("autocompleteMaxVisible")).toBe(20); + }); it("should coerce submenu string values for live editor updates", () => { const setAutocompleteMaxVisible = vi.fn();