fix: Project-level model config ignored — getLocalLayerValue missing model case

This commit is contained in:
kikuchi 2026-02-22 13:24:01 +09:00
parent e57612d703
commit 753deb6539
5 changed files with 60 additions and 0 deletions

View File

@ -501,6 +501,60 @@ describe('analytics config resolution', () => {
}); });
}); });
describe('model config resolution', () => {
let testDir: string;
let originalTaktConfigDir: string | undefined;
beforeEach(() => {
testDir = join(tmpdir(), `takt-test-${randomUUID()}`);
mkdirSync(testDir, { recursive: true });
originalTaktConfigDir = process.env.TAKT_CONFIG_DIR;
process.env.TAKT_CONFIG_DIR = join(testDir, 'global-takt');
invalidateGlobalConfigCache();
});
afterEach(() => {
if (originalTaktConfigDir === undefined) {
delete process.env.TAKT_CONFIG_DIR;
} else {
process.env.TAKT_CONFIG_DIR = originalTaktConfigDir;
}
invalidateGlobalConfigCache();
if (existsSync(testDir)) {
rmSync(testDir, { recursive: true, force: true });
}
});
it('should resolve project model over global model', () => {
const projectConfigDir = getProjectConfigDir(testDir);
mkdirSync(projectConfigDir, { recursive: true });
writeFileSync(join(projectConfigDir, 'config.yaml'), 'piece: default\nmodel: project-model\n');
const globalConfigDir = process.env.TAKT_CONFIG_DIR!;
mkdirSync(globalConfigDir, { recursive: true });
writeFileSync(join(globalConfigDir, 'config.yaml'), 'model: global-model\n');
expect(resolveConfigValue(testDir, 'model')).toBe('project-model');
});
it('should fallback to global model when project model is not set', () => {
const projectConfigDir = getProjectConfigDir(testDir);
mkdirSync(projectConfigDir, { recursive: true });
writeFileSync(join(projectConfigDir, 'config.yaml'), 'piece: default\n');
const globalConfigDir = process.env.TAKT_CONFIG_DIR!;
mkdirSync(globalConfigDir, { recursive: true });
writeFileSync(join(globalConfigDir, 'config.yaml'), 'model: global-model\n');
expect(resolveConfigValue(testDir, 'model')).toBe('global-model');
});
it('should return undefined when neither project nor global model is set', () => {
expect(resolveConfigValue(testDir, 'model')).toBeUndefined();
});
});
describe('isVerboseMode', () => { describe('isVerboseMode', () => {
let testDir: string; let testDir: string;
let originalTaktConfigDir: string | undefined; let originalTaktConfigDir: string | undefined;

View File

@ -129,6 +129,7 @@ export interface PersistedGlobalConfig {
export interface ProjectConfig { export interface ProjectConfig {
piece?: string; piece?: string;
provider?: 'claude' | 'codex' | 'opencode' | 'mock'; provider?: 'claude' | 'codex' | 'opencode' | 'mock';
model?: string;
providerOptions?: MovementProviderOptions; providerOptions?: MovementProviderOptions;
/** Provider-specific permission profiles */ /** Provider-specific permission profiles */
providerProfiles?: ProviderPermissionProfiles; providerProfiles?: ProviderPermissionProfiles;

View File

@ -487,6 +487,7 @@ export const GlobalConfigSchema = z.object({
export const ProjectConfigSchema = z.object({ export const ProjectConfigSchema = z.object({
piece: z.string().optional(), piece: z.string().optional(),
provider: z.enum(['claude', 'codex', 'opencode', 'mock']).optional(), provider: z.enum(['claude', 'codex', 'opencode', 'mock']).optional(),
model: z.string().optional(),
provider_options: MovementProviderOptionsSchema, provider_options: MovementProviderOptionsSchema,
provider_profiles: ProviderPermissionProfilesSchema, provider_profiles: ProviderPermissionProfilesSchema,
}); });

View File

@ -116,6 +116,8 @@ function getLocalLayerValue<K extends ConfigParameterKey>(
return project.piece as LoadedConfig[K] | undefined; return project.piece as LoadedConfig[K] | undefined;
case 'provider': case 'provider':
return project.provider as LoadedConfig[K] | undefined; return project.provider as LoadedConfig[K] | undefined;
case 'model':
return project.model as LoadedConfig[K] | undefined;
case 'autoPr': case 'autoPr':
return project.auto_pr as LoadedConfig[K] | undefined; return project.auto_pr as LoadedConfig[K] | undefined;
case 'draftPr': case 'draftPr':

View File

@ -12,6 +12,8 @@ export interface ProjectLocalConfig {
piece?: string; piece?: string;
/** Provider selection for agent runtime */ /** Provider selection for agent runtime */
provider?: 'claude' | 'codex' | 'opencode' | 'mock'; provider?: 'claude' | 'codex' | 'opencode' | 'mock';
/** Model selection for agent runtime */
model?: string;
/** Auto-create PR after worktree execution */ /** Auto-create PR after worktree execution */
auto_pr?: boolean; auto_pr?: boolean;
/** Create PR as draft */ /** Create PR as draft */