diff --git a/src/__tests__/it-piece-loader.test.ts b/src/__tests__/it-piece-loader.test.ts index 5ce414b..448b6dd 100644 --- a/src/__tests__/it-piece-loader.test.ts +++ b/src/__tests__/it-piece-loader.test.ts @@ -20,15 +20,22 @@ vi.mock('../infra/config/global/globalConfig.js', () => ({ loadGlobalConfig: vi.fn().mockReturnValue({}), })); -vi.mock('../infra/config/loadConfig.js', () => ({ - loadConfig: vi.fn(() => ({ - global: { - language: languageState.value, - disabledBuiltins: [], - enableBuiltinPieces: true, - }, - project: {}, - })), +vi.mock('../infra/config/resolveConfigValue.js', () => ({ + resolveConfigValue: vi.fn((_cwd: string, key: string) => { + if (key === 'language') return languageState.value; + if (key === 'enableBuiltinPieces') return true; + if (key === 'disabledBuiltins') return []; + return undefined; + }), + resolveConfigValues: vi.fn((_cwd: string, keys: readonly string[]) => { + const result: Record = {}; + for (const key of keys) { + if (key === 'language') result[key] = languageState.value; + if (key === 'enableBuiltinPieces') result[key] = true; + if (key === 'disabledBuiltins') result[key] = []; + } + return result; + }), })); // --- Imports (after mocks) --- diff --git a/src/__tests__/it-piece-patterns.test.ts b/src/__tests__/it-piece-patterns.test.ts index 88dce19..c320cca 100644 --- a/src/__tests__/it-piece-patterns.test.ts +++ b/src/__tests__/it-piece-patterns.test.ts @@ -57,14 +57,21 @@ vi.mock('../infra/config/project/projectConfig.js', () => ({ loadProjectConfig: vi.fn().mockReturnValue({}), })); -vi.mock('../infra/config/loadConfig.js', () => ({ - loadConfig: vi.fn().mockReturnValue({ - global: { - language: 'en', - enableBuiltinPieces: true, - disabledBuiltins: [], - }, - project: {}, +vi.mock('../infra/config/resolveConfigValue.js', () => ({ + resolveConfigValue: vi.fn((_cwd: string, key: string) => { + if (key === 'language') return 'en'; + if (key === 'enableBuiltinPieces') return true; + if (key === 'disabledBuiltins') return []; + return undefined; + }), + resolveConfigValues: vi.fn((_cwd: string, keys: readonly string[]) => { + const result: Record = {}; + for (const key of keys) { + if (key === 'language') result[key] = 'en'; + if (key === 'enableBuiltinPieces') result[key] = true; + if (key === 'disabledBuiltins') result[key] = []; + } + return result; }), })); diff --git a/src/__tests__/piece-builtin-toggle.test.ts b/src/__tests__/piece-builtin-toggle.test.ts index c5b00bd..65bb36c 100644 --- a/src/__tests__/piece-builtin-toggle.test.ts +++ b/src/__tests__/piece-builtin-toggle.test.ts @@ -17,15 +17,22 @@ vi.mock('../infra/config/global/globalConfig.js', async (importOriginal) => { }; }); -vi.mock('../infra/config/loadConfig.js', () => ({ - loadConfig: () => ({ - global: { - language: 'en', - enableBuiltinPieces: false, - disabledBuiltins: [], - }, - project: {}, - }), +vi.mock('../infra/config/resolveConfigValue.js', () => ({ + resolveConfigValue: (_cwd: string, key: string) => { + if (key === 'language') return 'en'; + if (key === 'enableBuiltinPieces') return false; + if (key === 'disabledBuiltins') return []; + return undefined; + }, + resolveConfigValues: (_cwd: string, keys: readonly string[]) => { + const result: Record = {}; + for (const key of keys) { + if (key === 'language') result[key] = 'en'; + if (key === 'enableBuiltinPieces') result[key] = false; + if (key === 'disabledBuiltins') result[key] = []; + } + return result; + }, })); const { listPieces } = await import('../infra/config/loaders/pieceLoader.js'); diff --git a/src/__tests__/piece-category-config.test.ts b/src/__tests__/piece-category-config.test.ts index a485a4d..475fc44 100644 --- a/src/__tests__/piece-category-config.test.ts +++ b/src/__tests__/piece-category-config.test.ts @@ -26,15 +26,22 @@ vi.mock('../infra/config/global/globalConfig.js', async (importOriginal) => { }; }); -vi.mock('../infra/config/loadConfig.js', () => ({ - loadConfig: () => ({ - global: { - language: languageState.value, - enableBuiltinPieces: true, - disabledBuiltins: [], - }, - project: {}, - }), +vi.mock('../infra/config/resolveConfigValue.js', () => ({ + resolveConfigValue: (_cwd: string, key: string) => { + if (key === 'language') return languageState.value; + if (key === 'enableBuiltinPieces') return true; + if (key === 'disabledBuiltins') return []; + return undefined; + }, + resolveConfigValues: (_cwd: string, keys: readonly string[]) => { + const result: Record = {}; + for (const key of keys) { + if (key === 'language') result[key] = languageState.value; + if (key === 'enableBuiltinPieces') result[key] = true; + if (key === 'disabledBuiltins') result[key] = []; + } + return result; + }, })); vi.mock('../infra/resources/index.js', async (importOriginal) => { diff --git a/src/infra/config/global/pieceCategories.ts b/src/infra/config/global/pieceCategories.ts index b0e50e1..bd59b1b 100644 --- a/src/infra/config/global/pieceCategories.ts +++ b/src/infra/config/global/pieceCategories.ts @@ -7,7 +7,7 @@ import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { getGlobalConfigDir } from '../paths.js'; -import { loadConfig } from '../loadConfig.js'; +import { resolveConfigValue } from '../resolveConfigValue.js'; const INITIAL_USER_CATEGORIES_CONTENT = 'piece_categories: {}\n'; @@ -17,9 +17,9 @@ function getDefaultPieceCategoriesPath(): string { /** Get the path to the user's piece categories file. */ export function getPieceCategoriesPath(cwd: string): string { - const config = loadConfig(cwd); - if (config.pieceCategoriesFile) { - return config.pieceCategoriesFile; + const pieceCategoriesFile = resolveConfigValue(cwd, 'pieceCategoriesFile'); + if (pieceCategoriesFile) { + return pieceCategoriesFile; } return getDefaultPieceCategoriesPath(); } diff --git a/src/infra/config/loaders/agentLoader.ts b/src/infra/config/loaders/agentLoader.ts index 8af69cf..97012cb 100644 --- a/src/infra/config/loaders/agentLoader.ts +++ b/src/infra/config/loaders/agentLoader.ts @@ -16,11 +16,11 @@ import { getBuiltinPiecesDir, isPathSafe, } from '../paths.js'; -import { loadConfig } from '../loadConfig.js'; +import { resolveConfigValue } from '../resolveConfigValue.js'; /** Get all allowed base directories for persona prompt files */ function getAllowedPromptBases(cwd: string): string[] { - const lang = loadConfig(cwd).language; + const lang = resolveConfigValue(cwd, 'language'); return [ getGlobalPersonasDir(), getGlobalPiecesDir(), diff --git a/src/infra/config/loaders/pieceCategories.ts b/src/infra/config/loaders/pieceCategories.ts index 135cdc3..d4f327d 100644 --- a/src/infra/config/loaders/pieceCategories.ts +++ b/src/infra/config/loaders/pieceCategories.ts @@ -13,7 +13,7 @@ import { z } from 'zod/v4'; import { getPieceCategoriesPath } from '../global/pieceCategories.js'; import { getLanguageResourcesDir } from '../../resources/index.js'; import { listBuiltinPieceNames } from './pieceResolver.js'; -import { loadConfig } from '../loadConfig.js'; +import { resolveConfigValues } from '../resolveConfigValue.js'; import type { PieceWithSource } from './pieceResolver.js'; const CategoryConfigSchema = z.object({ @@ -233,7 +233,7 @@ function resolveOthersCategoryName(defaultConfig: ParsedCategoryConfig, userConf * Returns null if file doesn't exist or has no piece_categories. */ export function loadDefaultCategories(cwd: string): CategoryConfig | null { - const lang = loadConfig(cwd).language; + const { language: lang } = resolveConfigValues(cwd, ['language']); const filePath = join(getLanguageResourcesDir(lang), 'piece-categories.yaml'); const parsed = loadCategoryConfigFromPath(filePath, filePath); @@ -256,7 +256,7 @@ export function loadDefaultCategories(cwd: string): CategoryConfig | null { /** Get the path to the builtin default categories file. */ export function getDefaultCategoriesPath(cwd: string): string { - const lang = loadConfig(cwd).language; + const { language: lang } = resolveConfigValues(cwd, ['language']); return join(getLanguageResourcesDir(lang), 'piece-categories.yaml'); } @@ -378,7 +378,7 @@ export function buildCategorizedPieces( config: CategoryConfig, cwd: string, ): CategorizedPieces { - const globalConfig = loadConfig(cwd); + const globalConfig = resolveConfigValues(cwd, ['enableBuiltinPieces', 'disabledBuiltins']); const ignoreMissing = new Set(); if (globalConfig.enableBuiltinPieces === false) { for (const name of listBuiltinPieceNames(cwd, { includeDisabled: true })) { diff --git a/src/infra/config/loaders/pieceParser.ts b/src/infra/config/loaders/pieceParser.ts index 12cfdbf..33ef929 100644 --- a/src/infra/config/loaders/pieceParser.ts +++ b/src/infra/config/loaders/pieceParser.ts @@ -11,7 +11,7 @@ import { parse as parseYaml } from 'yaml'; import type { z } from 'zod'; import { PieceConfigRawSchema, PieceMovementRawSchema } from '../../../core/models/index.js'; import type { PieceConfig, PieceMovement, PieceRule, OutputContractEntry, OutputContractItem, LoopMonitorConfig, LoopMonitorJudge, ArpeggioMovementConfig, ArpeggioMergeMovementConfig, TeamLeaderConfig } from '../../../core/models/index.js'; -import { loadConfig } from '../loadConfig.js'; +import { resolveConfigValue } from '../resolveConfigValue.js'; import { type PieceSections, type FacetResolutionContext, @@ -439,7 +439,7 @@ export function loadPieceFromFile(filePath: string, projectDir: string): PieceCo const pieceDir = dirname(filePath); const context: FacetResolutionContext = { - lang: loadConfig(projectDir).language, + lang: resolveConfigValue(projectDir, 'language'), projectDir, }; diff --git a/src/infra/config/loaders/pieceResolver.ts b/src/infra/config/loaders/pieceResolver.ts index fa4059f..f6871e0 100644 --- a/src/infra/config/loaders/pieceResolver.ts +++ b/src/infra/config/loaders/pieceResolver.ts @@ -10,7 +10,7 @@ import { join, resolve, isAbsolute } from 'node:path'; import { homedir } from 'node:os'; import type { PieceConfig, PieceMovement, InteractiveMode } from '../../../core/models/index.js'; import { getGlobalPiecesDir, getBuiltinPiecesDir, getProjectConfigDir } from '../paths.js'; -import { loadConfig } from '../loadConfig.js'; +import { resolveConfigValues } from '../resolveConfigValue.js'; import { createLogger, getErrorMessage } from '../../../shared/utils/index.js'; import { loadPieceFromFile } from './pieceParser.js'; @@ -24,7 +24,7 @@ export interface PieceWithSource { } export function listBuiltinPieceNames(cwd: string, options?: { includeDisabled?: boolean }): string[] { - const config = loadConfig(cwd); + const config = resolveConfigValues(cwd, ['language', 'disabledBuiltins']); const lang = config.language; const dir = getBuiltinPiecesDir(lang); const disabled = options?.includeDisabled ? undefined : (config.disabledBuiltins ?? []); @@ -37,7 +37,7 @@ export function listBuiltinPieceNames(cwd: string, options?: { includeDisabled?: /** Get builtin piece by name */ export function getBuiltinPiece(name: string, projectCwd: string): PieceConfig | null { - const config = loadConfig(projectCwd); + const config = resolveConfigValues(projectCwd, ['enableBuiltinPieces', 'language', 'disabledBuiltins']); if (config.enableBuiltinPieces === false) return null; const lang = config.language; const disabled = config.disabledBuiltins ?? []; @@ -373,7 +373,7 @@ function* iteratePieceDir( /** Get the 3-layer directory list (builtin → user → project-local) */ function getPieceDirs(cwd: string): { dir: string; source: PieceSource; disabled?: string[] }[] { - const config = loadConfig(cwd); + const config = resolveConfigValues(cwd, ['enableBuiltinPieces', 'language', 'disabledBuiltins']); const disabled = config.disabledBuiltins ?? []; const lang = config.language; const dirs: { dir: string; source: PieceSource; disabled?: string[] }[] = [];